// GPU texture readback utility implementation // Extracts texture pixels to CPU memory for offline processing #include "gpu/texture_readback.h" #if !defined(STRIP_ALL) #include #include #include // Callback state for async buffer mapping struct MapState { bool done = false; WGPUMapAsyncStatus status = WGPUMapAsyncStatus_Unknown; }; std::vector read_texture_pixels( WGPUInstance instance, WGPUDevice device, WGPUTexture texture, int width, int height) { // Align bytes per row to 256 (COPY_BYTES_PER_ROW_ALIGNMENT) const uint32_t bytes_per_pixel = 4; // BGRA8 const uint32_t unaligned_bytes_per_row = width * bytes_per_pixel; const uint32_t aligned_bytes_per_row = ((unaligned_bytes_per_row + 255) / 256) * 256; const size_t buffer_size = aligned_bytes_per_row * height; std::vector pixels(width * height * bytes_per_pixel); // Create staging buffer for readback (with aligned size) const WGPUBufferDescriptor buffer_desc = { .usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead, .size = buffer_size, }; WGPUBuffer staging = wgpuDeviceCreateBuffer(device, &buffer_desc); 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 = aligned_bytes_per_row, .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); WGPUQueue queue = wgpuDeviceGetQueue(device); wgpuQueueSubmit(queue, 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 (synchronous blocking) 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 (handle row padding) const uint8_t* mapped_data = static_cast( wgpuBufferGetConstMappedRange(staging, 0, buffer_size)); if (mapped_data) { // If rows are aligned, copy row by row to remove padding if (aligned_bytes_per_row != unaligned_bytes_per_row) { for (int y = 0; y < height; ++y) { memcpy(pixels.data() + y * unaligned_bytes_per_row, mapped_data + y * aligned_bytes_per_row, unaligned_bytes_per_row); } } else { // No padding, direct copy memcpy(pixels.data(), mapped_data, pixels.size()); } } // Cleanup wgpuBufferUnmap(staging); wgpuBufferRelease(staging); return pixels; } #endif // !defined(STRIP_ALL)