Skip to content

Commit

Permalink
test-renderers: Fix gltf-render test.
Browse files Browse the repository at this point in the history
It was broken due to some incomplete refactoring, and the use of
a different `wgpu` version than `rend3`.
  • Loading branch information
kpreid committed Aug 27, 2024
1 parent 286e64d commit 895614e
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 8 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions test-renderers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ auto-threads = [
]
gltf = [
"dep:all-is-cubes-port",
"dep:bytemuck",
"dep:futures-channel",
"dep:rend3",
"dep:rend3-gltf",
"dep:rend3-routine",
"dep:glam", # activates mint feature for interop
"dep:mint",
"dep:wgpu_for_rend3",
"tokio/fs",
]

Expand All @@ -69,10 +72,12 @@ all-is-cubes-port = { workspace = true, optional = true, features = ["export", "
all-is-cubes-render = { workspace = true, features = ["raytracer", "std"] }
all-is-cubes-ui = { workspace = true }
async_fn_traits = { workspace = true }
bytemuck = { workspace = true, optional = true }
# Using clap for parsing test command-line arguments
clap = { workspace = true }
exhaust = { workspace = true }
futures-core = { workspace = true }
futures-channel = { workspace = true, optional = true }
futures-util = { workspace = true, features = ["std"] }
# Must be the same version of glam as rend3 uses
glam = { version = "0.24", optional = true, features = ["mint"] }
Expand All @@ -99,6 +104,7 @@ tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "park
# In addition to depending on wgpu directly, we also need to select backends to be enabled in our binary.
# (As of wgpu 0.19, Vulkan is implicitly enabled on Linux.)
wgpu = { workspace = true, features = ["dx12", "metal"] }
wgpu_for_rend3 = { package = "wgpu", version = "0.19", optional = true, features = ["dx12", "metal"] }

[lints]
workspace = true
146 changes: 138 additions & 8 deletions test-renderers/tests/gltf-render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ use std::sync::Arc;

use clap::Parser as _;
use futures_core::future::BoxFuture;
use wgpu_for_rend3 as wgpu;

use all_is_cubes::space::{Sky, Space};
use all_is_cubes::universe::Handle;
use all_is_cubes_content::palette;
use all_is_cubes_port as port;
use all_is_cubes_render::camera::{Flaws, HeadlessRenderer, RenderError, StandardCameras};
use all_is_cubes_render::camera::StandardCameras;
use all_is_cubes_render::{Flaws, HeadlessRenderer, RenderError, Rendering};
use test_renderers::{RendererFactory, RendererId};

const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
Expand Down Expand Up @@ -256,17 +258,14 @@ impl HeadlessRenderer for GltfRend3Renderer {
})
}

fn draw<'a>(
&'a mut self,
info_text: &'a str,
) -> BoxFuture<'a, Result<all_is_cubes_render::camera::Rendering, RenderError>> {
fn draw<'a>(&'a mut self, info_text: &'a str) -> BoxFuture<'a, Result<Rendering, RenderError>> {
Box::pin(async {
let aic_camera = &self.cameras.cameras().world;
let viewport = aic_camera.viewport();

if viewport.pixel_count() == Some(0) {
// Empty viewport; must not try to use the GPU at all.
return Ok(all_is_cubes_render::camera::Rendering {
return Ok(Rendering {
size: viewport.framebuffer_size,
data: Vec::new(),
flaws: Flaws::empty(),
Expand All @@ -278,7 +277,7 @@ impl HeadlessRenderer for GltfRend3Renderer {
let (_objects, mut flaws) = self.load_latest_gltf().await?;

if !info_text.is_empty() {
flaws |= Flaws::MISSING_TEXTURES; // TODO: should have a flaw for this, or just actually implement it
flaws |= Flaws::OTHER; // TODO: should have a flaw for this, or just actually implement it
}

// not bothering to reuse texture
Expand All @@ -302,7 +301,7 @@ impl HeadlessRenderer for GltfRend3Renderer {

self.render_to_rend3(&frame_texture);

let rendering = all_is_cubes_gpu::in_wgpu::init::get_image_from_gpu(
let rendering = old_wgpu_helpers::get_image_from_gpu(
&self.renderer.device,
&self.renderer.queue,
&frame_texture,
Expand Down Expand Up @@ -344,3 +343,134 @@ fn convert_camera(aic_camera: &all_is_cubes_render::camera::Camera) -> rend3::ty
view: rotation_matrix * translation_matrix,
}
}

/// This is a copy and paste of all-is-cubes-gpu/src/in_wgpu/init.rs
/// except that it uses `rend3`'s version of wgpu instead. Yes
mod old_wgpu_helpers {
use super::*;
use all_is_cubes_render::camera;
use std::future::Future;
use std::sync::Arc;

/// Copy the contents of a texture into a [`Rendering`],
/// assuming that its byte layout is RGBA8.
///
/// Panics if the pixel type or viewport size are incorrect.
#[doc(hidden)]
pub fn get_image_from_gpu(
device: &Arc<wgpu::Device>,
queue: &wgpu::Queue,
texture: &wgpu::Texture,
flaws: Flaws,
) -> impl Future<Output = Rendering> + 'static {
// By making this an explicit `Future` return we avoid capturing the queue and texture
// references.

let size = camera::ImageSize::new(texture.width(), texture.height());
let data_future = get_texels_from_gpu::<[u8; 4]>(device, queue, texture, 1);

async move {
Rendering {
size,
data: data_future.await,
flaws,
}
}
}

/// Fetch the contents of a 2D texture, assuming that its byte layout is the same as that
/// of `[C; components]` and returning a vector of length
/// `dimensions.x * dimensions.y * components`.
///
/// Panics if the provided sizes are incorrect.
#[doc(hidden)]
pub fn get_texels_from_gpu<C>(
device: &Arc<wgpu::Device>,
queue: &wgpu::Queue,
texture: &wgpu::Texture,
components: usize,
) -> impl Future<Output = Vec<C>> + 'static
where
C: bytemuck::AnyBitPattern,
{
// By making this an explicit `Future` return we avoid capturing the queue and texture
// references.

let dimensions = camera::ImageSize::new(texture.width(), texture.height());
assert_eq!(texture.depth_or_array_layers(), 1);

// Check that the format matches
let format = texture.format();
let size_of_texel = components * size_of::<C>();
assert_eq!(
(format.block_copy_size(None), format.block_dimensions()),
(Some(size_of_texel as u32), (1, 1)),
"Texture format does not match requested size",
);

let dense_bytes_per_row = dimensions.width * u32::try_from(size_of_texel).unwrap();
let padded_bytes_per_row = dense_bytes_per_row.div_ceil(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
* wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;

let temp_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("GPU-to-CPU image copy buffer"),
size: u64::from(padded_bytes_per_row) * u64::from(dimensions.height),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});

{
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
encoder.copy_texture_to_buffer(
texture.as_image_copy(),
wgpu::ImageCopyBuffer {
buffer: &temp_buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(padded_bytes_per_row),
rows_per_image: None,
},
},
texture.size(),
);
queue.submit(Some(encoder.finish()));
}

// Start the buffer mapping
let (sender, receiver) = futures_channel::oneshot::channel();
temp_buffer
.slice(..)
.map_async(wgpu::MapMode::Read, |result| {
let _ = sender.send(result);
});
device.poll(wgpu::Maintain::Wait);

// Await the buffer being available and build the image.
async move {
receiver
.await
.expect("communication failed")
.expect("buffer reading failed");
let mapped: &[u8] = &temp_buffer.slice(..).get_mapped_range();

let element_count = camera::area_usize(dimensions).unwrap() * components;

// Copy the mapped buffer data into a Rust vector, removing row padding if present
// by copying it one row at a time.
let mut texel_vector: Vec<C> = Vec::with_capacity(element_count);
for row in 0..dimensions.height {
let byte_start_of_row = padded_bytes_per_row * row;
// TODO: this cast_slice() could fail if `C`’s alignment is higher than the buffer.
texel_vector.extend(bytemuck::cast_slice::<u8, C>(
&mapped[byte_start_of_row as usize..][..dense_bytes_per_row as usize],
));
}
debug_assert_eq!(texel_vector.len(), element_count);

temp_buffer.destroy();

texel_vector
}
}
}

0 comments on commit 895614e

Please sign in to comment.