1use super::image::ImageLayout;
4use super::image::{ImagePurpose, PUBLICATION_IMAGE_SAMPLES};
5use super::shadow::ShadowMatrices;
6use super::{Engine, ImageConfig, MotionBlur, QualityTier, TemporalOptions};
7use crate::error::RenderError;
8use crate::graph::ResourceId;
9use crate::passes::{DOF_RESOURCE, HISTORY_A_RESOURCE, HISTORY_B_RESOURCE, MOTION_BLUR_RESOURCE};
10use molgfx_core::Scene;
11use molgfx_gpu::{BufferDesc, BufferUsage, CommandEncoder as _, Device, Queue as _};
12use molgfx_math::Camera;
13
14#[derive(PartialEq, Eq, Debug)]
22pub struct HdrImage {
23 pub(super) width: u32,
25 pub(super) height: u32,
27 pub(super) rgba16f: Vec<u8>,
29}
30
31impl HdrImage {
32 #[must_use]
34 pub const fn width(&self) -> u32 {
35 self.width
36 }
37
38 #[must_use]
40 pub const fn height(&self) -> u32 {
41 self.height
42 }
43
44 #[must_use]
46 pub fn rgba16f(&self) -> &[u8] {
47 &self.rgba16f
48 }
49
50 pub fn write_exr(&self, writer: impl std::io::Write) -> Result<(), RenderError> {
60 super::exr::write(self, writer)
61 }
62}
63
64impl<D: Device> Engine<D> {
65 pub async fn render_hdr_image_async(
77 &mut self,
78 scene: &Scene,
79 camera: &Camera,
80 config: ImageConfig,
81 ) -> Result<HdrImage, RenderError> {
82 let pending = self.render_hdr_image_to_buffer(scene, camera, config)?;
83 let mapped = self
84 .queue
85 .read_buffer_async(&self.device, &pending.buffer, 0, pending.layout.buffer_size)
86 .await?;
87 pending.resolve(mapped)
88 }
89
90 #[cfg(not(target_arch = "wasm32"))]
100 pub fn render_hdr_image(
101 &mut self,
102 scene: &Scene,
103 camera: &Camera,
104 config: ImageConfig,
105 ) -> Result<HdrImage, RenderError> {
106 let pending = self.render_hdr_image_to_buffer(scene, camera, config)?;
107 let mapped = self.queue.read_buffer_blocking(
108 &self.device,
109 &pending.buffer,
110 0,
111 pending.layout.buffer_size,
112 )?;
113 pending.resolve(mapped)
114 }
115
116 fn render_hdr_image_to_buffer(
117 &mut self,
118 scene: &Scene,
119 camera: &Camera,
120 config: ImageConfig,
121 ) -> Result<PendingHdrImage<D>, RenderError> {
122 config.validate(self.device.capabilities().max_texture_dim)?;
123 let layout = ImageLayout::new(config, 8)?;
124 self.width = config.width;
125 self.height = config.height;
126 let preparation = self.prepare_image(scene, ImagePurpose::Publication)?;
127 self.temporal.reset();
128 self.temporal_scene_identity = None;
129 let optics = self.resolve_optics(scene, camera)?;
130 let readback = self.device.create_buffer(&BufferDesc {
131 label: "scene-linear HDR readback",
132 size: layout.buffer_size,
133 usage: BufferUsage::COPY_DST.union(BufferUsage::MAP_READ),
134 })?;
135 let samples = PUBLICATION_IMAGE_SAMPLES;
139 let shadow = self.shadow_bound.fit(
140 scene,
141 camera,
142 self.resolved_plan.lighting(),
143 preparation.scene_changed || preparation.rebuild,
144 );
145 for sample in 0..samples {
146 self.scene_gpu.begin_frame();
147 let quality = self.tier() >= QualityTier::Standard;
148 self.prepare_hdr_sample(camera, sample, quality, optics, shadow)?;
149 let source = self.scene_linear_resource();
150 let Some(pool) = &self.pool else {
151 return Err(molgfx_gpu::GpuError::DeviceLost.into());
152 };
153 let Some(target) = pool.view(source) else {
154 return Err(molgfx_gpu::GpuError::DeviceLost.into());
155 };
156 let mut encoder = self.device.create_command_encoder();
157 self.passes
158 .cull
159 .record_attribute_timelines(&self.scene_gpu, &mut encoder);
160 self.passes
161 .cull
162 .record_instance_timelines(&self.scene_gpu, &mut encoder);
163 let point_coordinates_changed = self
164 .passes
165 .cull
166 .record_point_timelines(&self.scene_gpu, &mut encoder);
167 self.scene_gpu
168 .record_particle_motion(&mut encoder, &self.passes.particle_motion);
169 let structure_coordinates_changed =
170 self.scene_gpu
171 .record_trajectories(&mut encoder, &self.passes.trajectory, None);
172 let paged_coordinates_changed = self
173 .passes
174 .cull
175 .record_paged_trajectories(&self.scene_gpu, &mut encoder);
176 self.scene_gpu.record_dynamic_relations(
177 &mut encoder,
178 &self.passes.relation_resolve,
179 structure_coordinates_changed
180 || paged_coordinates_changed
181 || point_coordinates_changed,
182 );
183 self.scene_gpu
184 .record_occupancies(&mut encoder, self.passes.occupancy.as_ref());
185 self.scene_gpu.record_surface_fields(
186 &mut encoder,
187 &self.passes.surface_field,
188 &self.passes.surface_components,
189 );
190 self.scene_gpu
191 .record_quality_hardware(&mut encoder, quality);
192 self.record_image_until(&mut encoder, target, None, quality, false, Some(source));
193 if sample + 1 == samples {
194 let Some(texture) = pool.texture(source) else {
195 return Err(molgfx_gpu::GpuError::DeviceLost.into());
196 };
197 encoder.copy_texture_to_buffer(
198 texture,
199 (0, 0),
200 (config.width, config.height),
201 layout.padded_row,
202 0,
203 &readback,
204 );
205 }
206 self.queue.submit(encoder);
207 }
208 Ok(PendingHdrImage {
209 config,
210 layout,
211 buffer: readback,
212 })
213 }
214
215 fn prepare_hdr_sample(
216 &mut self,
217 camera: &Camera,
218 sample: u32,
219 quality: bool,
220 optics: [f32; 4],
221 shadow: ShadowMatrices,
222 ) -> Result<(), RenderError> {
223 let uniforms = self.temporal.prepare(
224 camera,
225 &TemporalOptions {
226 extent: [self.width, self.height],
227 reset: sample == 0,
228 quality,
229 publication: true,
230 illustration: self.resolved_plan.illustration(),
231 optics,
232 motion_blur: self
233 .resolved_plan
234 .motion_blur()
235 .map_or([0.0; 4], MotionBlur::packed),
236 atmosphere: self
237 .resolved_plan
238 .packed_presentation(self.scene_gpu.has_translucency()),
239 lighting: self.resolved_plan.packed_lighting(),
240 shadow_view: shadow.view,
241 shadow_projection: shadow.projection,
242 shadow_view_proj: shadow.view_projection,
243 },
244 );
245 self.scene_gpu.write_frame_uniforms(&self.queue, &uniforms)
246 }
247
248 fn scene_linear_resource(&self) -> ResourceId {
249 if self.resolved_plan.motion_blur().is_some() {
250 MOTION_BLUR_RESOURCE
251 } else if self.resolved_plan.depth_of_field().is_some() {
252 DOF_RESOURCE
253 } else if self.temporal.write_index() == 0 {
254 HISTORY_A_RESOURCE
255 } else {
256 HISTORY_B_RESOURCE
257 }
258 }
259}
260
261struct PendingHdrImage<D: Device> {
262 config: ImageConfig,
263 layout: ImageLayout,
264 buffer: D::Buffer,
265}
266
267impl<D: Device> PendingHdrImage<D> {
268 fn resolve(self, mapped: Vec<u8>) -> Result<HdrImage, RenderError> {
269 Ok(HdrImage {
270 width: self.config.width,
271 height: self.config.height,
272 rgba16f: self.layout.unpack(mapped)?,
273 })
274 }
275}
276
277#[cfg(all(test, not(target_arch = "wasm32")))]
278#[path = "hdr_image_tests.rs"]
279mod tests;