use crate::backend::{GpuBackend, RenderTargetHandle};
use crate::device::Device;
use crate::encoder::CommandEncoder;
use crate::types::*;
use anyhow::Result;
use std::sync::{Arc, Mutex};
pub struct RenderTarget {
backend: Arc<Mutex<Box<dyn GpuBackend>>>,
device_handle: u64,
handle: RenderTargetHandle,
width: u32,
height: u32,
format: TextureFormat,
depth_format: Option<DepthFormat>,
}
impl RenderTarget {
pub fn new(device: &Device, width: u32, height: u32, format: TextureFormat) -> Result<Self> {
Self::new_with_depth(device, width, height, format, None)
}
pub fn new_with_depth(
device: &Device,
width: u32,
height: u32,
color_format: TextureFormat,
depth_format: Option<DepthFormat>,
) -> Result<Self> {
let handle = {
let mut backend = device.backend.lock().unwrap();
backend.create_render_target_with_depth(device.handle, width, height, color_format, depth_format)?
};
Ok(Self {
backend: Arc::clone(&device.backend),
device_handle: device.handle,
handle,
width,
height,
format: color_format,
depth_format,
})
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn format(&self) -> TextureFormat {
self.format
}
pub fn depth_format(&self) -> Option<DepthFormat> {
self.depth_format
}
pub fn has_depth(&self) -> bool {
self.depth_format.is_some()
}
pub fn buffer_size(&self) -> usize {
(self.width * self.height * self.format.bytes_per_pixel()) as usize
}
pub fn render(&self, encoder: CommandEncoder) -> Result<()> {
let commands = encoder.finish();
let mut backend = self.backend.lock().unwrap();
backend.render_to_target(self.device_handle, self.handle, &commands)
}
pub fn read_to_cpu(&self) -> Result<Vec<u8>> {
let mut output = vec![0u8; self.buffer_size()];
self.read_to_buffer(&mut output)?;
Ok(output)
}
pub fn read_to_buffer(&self, output: &mut [u8]) -> Result<()> {
let mut backend = self.backend.lock().unwrap();
backend.read_target_to_cpu(self.handle, output)
}
}
impl Drop for RenderTarget {
fn drop(&mut self) {
if let Ok(mut backend) = self.backend.lock() {
backend.destroy_render_target(self.handle);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::mock::MockBackend;
fn create_test_device() -> Device {
Device::from_backend(Box::new(MockBackend::new())).unwrap()
}
#[test]
fn test_render_target_creation() {
let device = create_test_device();
let target = RenderTarget::new(&device, 800, 600, TextureFormat::Rgba8Unorm).unwrap();
assert_eq!(target.width(), 800);
assert_eq!(target.height(), 600);
assert_eq!(target.format(), TextureFormat::Rgba8Unorm);
assert_eq!(target.buffer_size(), 800 * 600 * 4);
}
#[test]
fn test_render_without_readback() {
let device = create_test_device();
let target = RenderTarget::new(&device, 100, 100, TextureFormat::Rgba8Unorm).unwrap();
let mut encoder = CommandEncoder::new();
{
let mut pass = encoder.begin_render_pass();
pass.clear(Color::RED);
}
target.render(encoder).unwrap();
}
#[test]
fn test_explicit_readback() {
let device = create_test_device();
let target = RenderTarget::new(&device, 2, 2, TextureFormat::Rgba8Unorm).unwrap();
let mut encoder = CommandEncoder::new();
{
let mut pass = encoder.begin_render_pass();
pass.clear(Color::RED);
}
target.render(encoder).unwrap();
let pixels = target.read_to_cpu().unwrap();
assert_eq!(pixels.len(), 2 * 2 * 4);
assert_eq!(pixels[0], 255);
assert_eq!(pixels[1], 0);
assert_eq!(pixels[2], 0);
assert_eq!(pixels[3], 255);
}
#[test]
fn test_read_to_buffer() {
let device = create_test_device();
let target = RenderTarget::new(&device, 2, 2, TextureFormat::Rgba8Unorm).unwrap();
let mut encoder = CommandEncoder::new();
{
let mut pass = encoder.begin_render_pass();
pass.clear(Color::GREEN);
}
target.render(encoder).unwrap();
let mut buffer = vec![0u8; target.buffer_size()];
target.read_to_buffer(&mut buffer).unwrap();
assert_eq!(buffer[0], 0);
assert_eq!(buffer[1], 255);
assert_eq!(buffer[2], 0);
assert_eq!(buffer[3], 255);
}
#[test]
fn test_multiple_renders() {
let device = create_test_device();
let target = RenderTarget::new(&device, 10, 10, TextureFormat::Rgba8Unorm).unwrap();
for color in [Color::RED, Color::GREEN, Color::BLUE] {
let mut encoder = CommandEncoder::new();
{
let mut pass = encoder.begin_render_pass();
pass.clear(color);
}
target.render(encoder).unwrap();
}
let pixels = target.read_to_cpu().unwrap();
assert_eq!(pixels[0], 0); assert_eq!(pixels[1], 0); assert_eq!(pixels[2], 255); assert_eq!(pixels[3], 255); }
#[test]
fn test_render_target_with_depth() {
let device = create_test_device();
let target = RenderTarget::new_with_depth(
&device,
800,
600,
TextureFormat::Rgba8Unorm,
Some(DepthFormat::Depth24Plus),
).unwrap();
assert_eq!(target.width(), 800);
assert_eq!(target.height(), 600);
assert_eq!(target.format(), TextureFormat::Rgba8Unorm);
assert_eq!(target.depth_format(), Some(DepthFormat::Depth24Plus));
assert!(target.has_depth());
}
#[test]
fn test_render_target_without_depth() {
let device = create_test_device();
let target = RenderTarget::new(&device, 100, 100, TextureFormat::Rgba8Unorm).unwrap();
assert_eq!(target.depth_format(), None);
assert!(!target.has_depth());
}
#[test]
fn test_render_with_depth_clear() {
let device = create_test_device();
let target = RenderTarget::new_with_depth(
&device,
100,
100,
TextureFormat::Rgba8Unorm,
Some(DepthFormat::Depth32Float),
).unwrap();
let mut encoder = CommandEncoder::new();
{
let mut pass = encoder.begin_render_pass();
pass.clear(Color::CORNFLOWER_BLUE);
pass.clear_depth(1.0); }
target.render(encoder).unwrap();
}
}