Loading...
Searching...
No Matches
CubemapLoader.hpp
1#pragma once
2
3#include <halp/controls.hpp>
4#include <halp/file_port.hpp>
5#include <halp/meta.hpp>
6#include <halp/texture.hpp>
7
8#include <Gfx/Graph/RenderList.hpp>
9
10#include <ossia/dataflow/geometry_port.hpp>
11
12#include <QtGui/private/qrhi_p.h>
13
14#include <QDebug>
15#include <QImage>
16
17#include <cstdint>
18#include <functional>
19#include <memory>
20
21namespace Threedim
22{
23
24enum class CubemapLayout
25{
26 Equirectangular,
27 HorizontalCross,
28 VerticalCross,
29 HorizontalStrip,
30 VerticalStrip,
31};
32
34{
35public:
36 halp_meta(name, "Cubemap Loader")
37 halp_meta(category, "Visuals/3D")
38 halp_meta(c_name, "cubemap_loader")
39 halp_meta(description, "Load an environment image into a cubemap and scene skybox contribution.")
40 halp_meta(
41 manual_url,
42 "https://ossia.io/score-docs/processes/cubemap-loader.html")
43 halp_meta(uuid, "b8d4f9e2-6c3a-4b0f-9e4d-2a7f0c5b3d6e")
44
45 struct ins
46 {
47 // File-port boilerplate — same pattern as ImageLoader. process()
48 // runs on the file-load worker thread, decodes the image off the
49 // render thread, returns a lambda that stages the decoded QImage
50 // onto the node from the execution thread.
51 struct image_t : halp::file_port<"Image", halp::mmap_file_view>
52 {
53 // Only LDR formats are advertised: decode goes through QImage, which has
54 // no stock Radiance-HDR / OpenEXR handler, and both the equirect source
55 // and the cube textures are RGBA8. Listing .hdr/.exr would decode to null
56 // or truncate to 8 bits, since no float decoder is vendored here.
57 halp_meta(extensions,
58 "Images (*.png *.jpg *.jpeg *.bmp *.tga *.webp *.tif *.tiff)");
59 static std::function<void(CubemapLoader&)> process(file_type data)
60 {
61 QImage img;
62 if(!data.bytes.empty())
63 {
64 img.loadFromData(
65 reinterpret_cast<const uchar*>(data.bytes.data()),
66 (int)data.bytes.size());
67 }
68 if(img.isNull() && !data.filename.empty())
69 {
70 img = QImage(data.filename.data());
71 }
72 if(img.isNull())
73 {
74 // Never fail silently — surface unsupported/corrupt inputs
75 // (e.g. an HDR/EXR file QImage cannot decode).
76 qWarning() << "CubemapLoader: failed to decode environment image"
77 << data.filename.data()
78 << "- unsupported or corrupt format (HDR/EXR float"
79 " formats are not supported)";
80 }
81 else if(img.format() != QImage::Format_RGBA8888)
82 img = img.convertToFormat(QImage::Format_RGBA8888);
83 return [img = std::move(img)](CubemapLoader& self) mutable {
84 self.m_loadedImage = std::move(img);
85 self.m_imageChanged = true;
86 };
87 }
88 } image;
89
90 struct : halp::enum_t<CubemapLayout, "Layout">
91 {
92 void update(CubemapLoader& self) { self.m_imageChanged = true; }
93 } layout;
94
95 struct : halp::spinbox_i32<"Resolution", halp::range{16, 8192, 512}>
96 {
97 void update(CubemapLoader& self) { self.m_imageChanged = true; }
98 } resolution;
99 } inputs;
100
101 struct
102 {
103 // Raw cube texture — kept for consumers that want the handle
104 // directly (e.g. a bare-skybox rendering shader). Tagged via
105 // halp::gpu_cubemap_output so sinks know to grab-from-source
106 // rather than allocate a 2D render target.
107 halp::gpu_cubemap_output<"Cubemap"> cubemap;
108
109 // Scene-graph output: a scene_spec whose scene_environment has only
110 // skybox_texture.native_handle populated (no ambient / fog / etc.,
111 // no roots). Lets users wire the cubemap into a scene without a
112 // side-channel cable: merge_scenes's per-field env overlay folds
113 // it together with an EnvironmentLoader's params independent of
114 // wiring order.
115 struct
116 {
117 halp_meta(name, "Scene");
118 ossia::scene_spec scene;
119 uint8_t dirty{0};
120 } scene_out;
121 } outputs;
122
123 // Stable scene_state identity so downstream scene-identity caches
124 // (ScenePreprocessor, merge_scenes passthrough) stay hot across frames.
125 std::shared_ptr<ossia::scene_state> m_sceneState;
126 int64_t m_sceneVersion{0};
127 void* m_lastPublishedHandle{};
128
129 // GPU resources
130 QRhiTexture* m_cubemapTex{};
131 QRhiTexture* m_equirectTex{};
132 QRhiSampler* m_equirectSampler{};
133
134 // Per-face render targets for equirectangular conversion
135 struct FaceRT
136 {
137 QRhiTextureRenderTarget* renderTarget{};
138 QRhiRenderPassDescriptor* renderPass{};
139 };
140 FaceRT m_faceRTs[6]{};
141 QRhiGraphicsPipeline* m_equirectPipeline{};
142 QRhiShaderResourceBindings* m_equirectSrb{};
143 QRhiBuffer* m_equirectUbo{};
144 QRhiBuffer* m_quadVbuf{};
145
146 // Per-face stride into m_equirectUbo. The 6 equirect->cube face passes
147 // each need their own FaceInfo slot bound via a dynamic offset, so the
148 // UBO holds 6 blocks spaced by aligned(UniformBufferOffsetAlignment).
149 quint32 m_equirectUboStride{0};
150
151 int m_faceSize{0};
152 bool m_imageChanged{true};
153 QImage m_loadedImage;
154
155 void operator()() { }
156
157 // Dtor safety net: if the renderer framework's release(RenderList&)
158 // path was skipped (e.g. a reconcile path that deletes the renderer
159 // without first calling release — or any future code that drops the
160 // GfxRenderer's shared_ptr<CubemapLoader> without going through
161 // CpuFilterNode::releaseState), any still-live textures and GPU
162 // resources go to deleteLater here so QRhi's destructor can collect
163 // them before vkDestroyDevice. Without this the Vulkan validation
164 // layer flags "VkImage has not been destroyed" on app exit.
165 ~CubemapLoader();
166
167 void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res);
168 void update(
169 score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res,
171 void release(score::gfx::RenderList& r);
172 void runInitialPasses(
173 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
174 QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge);
175
176private:
177 bool createCubemapTexture(QRhi& rhi, int faceSize);
178 void releaseCubemapTexture();
179 // `renderer` is optional: when non-null, QRhiBuffers go through
180 // RenderList::releaseBuffer (the project-wide lifetime invariant);
181 // when null (dtor fallback, after the RenderList itself may have
182 // already been destroyed), they go straight to deleteLater.
183 // Textures always deleteLater directly — they're not tracked in
184 // RenderList::m_vertexBuffers, so the double-free risk only applies
185 // to buffers.
186 void releaseEquirectResources(score::gfx::RenderList* renderer = nullptr);
187
188 void uploadCrossOrStrip(QRhiResourceUpdateBatch* res);
189 void renderEquirectangular(
190 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
191 QRhiResourceUpdateBatch*& res);
192
193 bool setupEquirectPipeline(score::gfx::RenderList& renderer);
194
195 // Extract face from cross/strip layout
196 QImage extractFace(int faceIndex) const;
197};
198
199}
Definition CubemapLoader.hpp:34
List of nodes to be rendered to an output.
Definition RenderList.hpp:36
Definition CubemapLoader.hpp:136
Definition CubemapLoader.hpp:52
Definition CubemapLoader.hpp:46
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:236