use concinnity_core::gfx::image_decode::{self, PixelLayout};
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::hdr_output::HdrEncoding;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use super::com;
use super::context::DxContext;
use super::texture::{one_shot_submit, transition_barrier};
use crate::directx::error::map_hresult;
use crate::png_encode::encode_png;
impl DxContext {
pub(crate) fn capture_screenshot(&mut self, path: &str) -> RenderResult<String> {
let Some(back_idx) = self.swapchain.last_present_index else {
return Err(RenderError::Other(
"screenshot: no frame has been presented yet".into(),
));
};
let back_buffer = self
.swapchain
.back_buffers
.get(back_idx)
.ok_or_else(|| RenderError::Other("screenshot: stale back-buffer index".into()))?
.clone();
let width = self.targets.extent.output_width;
let height = self.targets.extent.output_height;
if width == 0 || height == 0 {
return Err(RenderError::Other(
"screenshot: zero-sized swapchain".into(),
));
}
self.wait_idle();
let tex_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: self.swapchain.format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut layout = D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default();
let mut row_count: u32 = 0;
let mut row_size: u64 = 0;
let mut total_size: u64 = 0;
unsafe {
self.hw.device.GetCopyableFootprints(
&tex_desc,
0,
1,
0,
Some(&mut layout),
Some(&mut row_count),
Some(&mut row_size),
Some(&mut total_size),
);
}
let readback = self.hw.alloc.alloc_buffer(
total_size,
D3D12_HEAP_TYPE_READBACK,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
let dst_loc = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&readback),
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
PlacedFootprint: layout,
},
};
let src_loc = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&back_buffer),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: 0,
},
};
one_shot_submit(&self.hw.device, &self.hw.command_queue, |cmd| unsafe {
let to_src = transition_barrier(
&back_buffer,
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_COPY_SOURCE,
);
cmd.ResourceBarrier(&[to_src]);
cmd.CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, None);
let to_present = transition_barrier(
&back_buffer,
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_PRESENT,
);
cmd.ResourceBarrier(&[to_present]);
})?;
let row_pitch = layout.Footprint.RowPitch as usize;
let tight_row = row_size as usize;
let mut map_ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { readback.Map(0, None, Some(&mut map_ptr)) }
.map_err(|e| map_hresult(e.code(), "screenshot: map readback"))?;
let mut packed = vec![0u8; tight_row * height as usize];
for row in 0..height as usize {
let src = unsafe { (map_ptr as *const u8).add(row * row_pitch) };
let src_slice = unsafe { std::slice::from_raw_parts(src, tight_row) };
packed[row * tight_row..(row + 1) * tight_row].copy_from_slice(src_slice);
}
unsafe { readback.Unmap(0, None) };
let rgba = image_decode::decode_to_rgba8(
&packed,
classify(self.swapchain.format, self.hw.hdr_encoding()),
);
encode_png(path, width, height, &rgba)?;
Ok(path.to_string())
}
}
#[cfg(test)]
fn swapchain_bytes_per_pixel(format: DXGI_FORMAT) -> u32 {
match format {
DXGI_FORMAT_R16G16B16A16_FLOAT => 8,
_ => 4,
}
}
fn classify(format: DXGI_FORMAT, encoding: Option<HdrEncoding>) -> PixelLayout {
match format {
DXGI_FORMAT_R16G16B16A16_FLOAT => PixelLayout::Rgba16F {
scrgb: !matches!(encoding, Some(HdrEncoding::Pq)),
},
DXGI_FORMAT_B8G8R8A8_UNORM | DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => PixelLayout::Bgra8,
_ => PixelLayout::Rgba8,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bytes_per_pixel_matches_swapchain_formats() {
assert_eq!(swapchain_bytes_per_pixel(DXGI_FORMAT_B8G8R8A8_UNORM), 4);
assert_eq!(swapchain_bytes_per_pixel(DXGI_FORMAT_R8G8B8A8_UNORM), 4);
assert_eq!(swapchain_bytes_per_pixel(DXGI_FORMAT_R16G16B16A16_FLOAT), 8);
}
#[test]
fn classify_maps_swapchain_formats_to_pixel_layouts() {
assert_eq!(
classify(DXGI_FORMAT_B8G8R8A8_UNORM, None),
PixelLayout::Bgra8
);
assert_eq!(
classify(DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, None),
PixelLayout::Bgra8
);
assert_eq!(
classify(DXGI_FORMAT_R8G8B8A8_UNORM, None),
PixelLayout::Rgba8
);
assert_eq!(
classify(
DXGI_FORMAT_R16G16B16A16_FLOAT,
Some(HdrEncoding::ExtendedLinear)
),
PixelLayout::Rgba16F { scrgb: true }
);
assert_eq!(
classify(DXGI_FORMAT_R16G16B16A16_FLOAT, Some(HdrEncoding::Pq)),
PixelLayout::Rgba16F { scrgb: false }
);
assert_eq!(
classify(DXGI_FORMAT_R16G16B16A16_FLOAT, None),
PixelLayout::Rgba16F { scrgb: true }
);
}
}