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