summaryrefslogtreecommitdiff
path: root/src/tests/offscreen_render_target.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/offscreen_render_target.cc')
-rw-r--r--src/tests/offscreen_render_target.cc170
1 files changed, 170 insertions, 0 deletions
diff --git a/src/tests/offscreen_render_target.cc b/src/tests/offscreen_render_target.cc
new file mode 100644
index 0000000..81ad082
--- /dev/null
+++ b/src/tests/offscreen_render_target.cc
@@ -0,0 +1,170 @@
+// This file is part of the 64k demo project.
+// It implements offscreen rendering for headless GPU testing.
+// Provides pixel readback for validation.
+
+#if !defined(STRIP_ALL) // Test code only - zero size impact on final binary
+
+#include "offscreen_render_target.h"
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+
+OffscreenRenderTarget::OffscreenRenderTarget(WGPUInstance instance,
+ WGPUDevice device,
+ int width,
+ int height,
+ WGPUTextureFormat format)
+ : instance_(instance),
+ device_(device),
+ width_(width),
+ height_(height),
+ format_(format) {
+ // Create offscreen texture
+ const WGPUTextureDescriptor texture_desc = {
+ .usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc,
+ .dimension = WGPUTextureDimension_2D,
+ .size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1},
+ .format = format,
+ .mipLevelCount = 1,
+ .sampleCount = 1,
+ };
+ texture_ = wgpuDeviceCreateTexture(device_, &texture_desc);
+ assert(texture_ && "Failed to create offscreen texture");
+
+ // Create texture view
+ const WGPUTextureViewDescriptor view_desc = {
+ .format = format,
+ .dimension = WGPUTextureViewDimension_2D,
+ .baseMipLevel = 0,
+ .mipLevelCount = 1,
+ .baseArrayLayer = 0,
+ .arrayLayerCount = 1,
+ };
+ view_ = wgpuTextureCreateView(texture_, &view_desc);
+ assert(view_ && "Failed to create offscreen texture view");
+}
+
+OffscreenRenderTarget::~OffscreenRenderTarget() {
+ if (view_) {
+ wgpuTextureViewRelease(view_);
+ }
+ if (texture_) {
+ wgpuTextureRelease(texture_);
+ }
+}
+
+void OffscreenRenderTarget::map_callback(WGPUMapAsyncStatus status,
+ void* userdata) {
+ MapState* state = static_cast<MapState*>(userdata);
+ state->status = status;
+ state->done = true;
+}
+
+WGPUBuffer OffscreenRenderTarget::create_staging_buffer() {
+ const size_t buffer_size = width_ * height_ * 4; // BGRA8 = 4 bytes/pixel
+ const WGPUBufferDescriptor buffer_desc = {
+ .usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead,
+ .size = buffer_size,
+ };
+ return wgpuDeviceCreateBuffer(device_, &buffer_desc);
+}
+
+std::vector<uint8_t> OffscreenRenderTarget::read_pixels() {
+ const size_t buffer_size = width_ * height_ * 4; // BGRA8
+ std::vector<uint8_t> pixels(buffer_size);
+
+ // Create staging buffer for readback
+ WGPUBuffer staging = create_staging_buffer();
+ assert(staging && "Failed to create staging buffer");
+
+ // Create command encoder for copy operation
+ const WGPUCommandEncoderDescriptor enc_desc = {};
+ WGPUCommandEncoder encoder =
+ wgpuDeviceCreateCommandEncoder(device_, &enc_desc);
+
+ // Copy texture to buffer
+ const WGPUTexelCopyTextureInfo src = {
+ .texture = texture_,
+ .mipLevel = 0,
+ .origin = {0, 0, 0},
+ };
+
+ const WGPUTexelCopyBufferInfo dst = {
+ .buffer = staging,
+ .layout =
+ {
+ .bytesPerRow = static_cast<uint32_t>(width_ * 4),
+ .rowsPerImage = static_cast<uint32_t>(height_),
+ },
+ };
+
+ const WGPUExtent3D copy_size = {static_cast<uint32_t>(width_),
+ static_cast<uint32_t>(height_), 1};
+
+ wgpuCommandEncoderCopyTextureToBuffer(encoder, &src, &dst, &copy_size);
+
+ // Submit commands
+ WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr);
+ wgpuQueueSubmit(wgpuDeviceGetQueue(device_), 1, &commands);
+ wgpuCommandBufferRelease(commands);
+ wgpuCommandEncoderRelease(encoder);
+
+ // Map buffer for reading (API differs between Win32 and native)
+#if defined(DEMO_CROSS_COMPILE_WIN32)
+ // Win32: Old callback API
+ MapState map_state = {};
+ auto map_cb = [](WGPUBufferMapAsyncStatus status, void* userdata) {
+ MapState* state = static_cast<MapState*>(userdata);
+ state->status = status;
+ state->done = true;
+ };
+ wgpuBufferMapAsync(staging, WGPUMapMode_Read, 0, buffer_size, map_cb,
+ &map_state);
+#else
+ // Native: New callback info API
+ MapState map_state = {};
+ auto map_cb = [](WGPUMapAsyncStatus status, WGPUStringView message,
+ void* userdata, void* user2) {
+ (void)message;
+ (void)user2;
+ MapState* state = static_cast<MapState*>(userdata);
+ state->status = status;
+ state->done = true;
+ };
+ WGPUBufferMapCallbackInfo map_info = {};
+ map_info.mode = WGPUCallbackMode_WaitAnyOnly;
+ map_info.callback = map_cb;
+ map_info.userdata1 = &map_state;
+ wgpuBufferMapAsync(staging, WGPUMapMode_Read, 0, buffer_size, map_info);
+#endif
+
+ // Wait for mapping to complete
+ for (int i = 0; i < 100 && !map_state.done; ++i) {
+#if defined(__EMSCRIPTEN__)
+ emscripten_sleep(10);
+#else
+ wgpuInstanceProcessEvents(instance_);
+#endif
+ }
+
+ if (map_state.status != WGPUMapAsyncStatus_Success) {
+ fprintf(stderr, "Buffer mapping failed: %d\n", map_state.status);
+ wgpuBufferRelease(staging);
+ return pixels; // Return empty
+ }
+
+ // Copy data from mapped buffer
+ const uint8_t* mapped_data = static_cast<const uint8_t*>(
+ wgpuBufferGetConstMappedRange(staging, 0, buffer_size));
+ if (mapped_data) {
+ memcpy(pixels.data(), mapped_data, buffer_size);
+ }
+
+ // Cleanup
+ wgpuBufferUnmap(staging);
+ wgpuBufferRelease(staging);
+
+ return pixels;
+}
+
+#endif /* !defined(STRIP_ALL) */