// 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 #include #include 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(width), static_cast(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(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 OffscreenRenderTarget::read_pixels() { const size_t buffer_size = width_ * height_ * 4; // BGRA8 std::vector 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(width_ * 4), .rowsPerImage = static_cast(height_), }, }; const WGPUExtent3D copy_size = {static_cast(width_), static_cast(height_), 1}; wgpuCommandEncoderCopyTextureToBuffer(encoder, &src, &dst, ©_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(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(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( 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) */