summaryrefslogtreecommitdiff
path: root/src/gpu
diff options
context:
space:
mode:
Diffstat (limited to 'src/gpu')
-rw-r--r--src/gpu/texture_readback.cc143
-rw-r--r--src/gpu/texture_readback.h23
2 files changed, 166 insertions, 0 deletions
diff --git a/src/gpu/texture_readback.cc b/src/gpu/texture_readback.cc
new file mode 100644
index 0000000..3a690d3
--- /dev/null
+++ b/src/gpu/texture_readback.cc
@@ -0,0 +1,143 @@
+// GPU texture readback utility implementation
+// Extracts texture pixels to CPU memory for offline processing
+
+#include "gpu/texture_readback.h"
+
+#if !defined(STRIP_ALL)
+
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+
+// Callback state for async buffer mapping
+struct MapState {
+ bool done = false;
+ WGPUMapAsyncStatus status = WGPUMapAsyncStatus_Unknown;
+};
+
+std::vector<uint8_t> 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<uint8_t> 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<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);
+ 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<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 (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<const uint8_t*>(
+ 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)
diff --git a/src/gpu/texture_readback.h b/src/gpu/texture_readback.h
new file mode 100644
index 0000000..1bf770f
--- /dev/null
+++ b/src/gpu/texture_readback.h
@@ -0,0 +1,23 @@
+// GPU texture readback utility for offline processing
+// Synchronous blocking operation (waits for GPU completion)
+
+#pragma once
+
+// Protected with STRIP_ALL: only needed for dev tools, not final release
+#if !defined(STRIP_ALL)
+
+#include "platform/platform.h"
+#include <vector>
+#include <cstdint>
+
+// Read texture pixels to CPU memory (synchronous, blocking)
+// Format: BGRA8Unorm (4 bytes per pixel)
+// Returns: width * height * 4 bytes
+std::vector<uint8_t> read_texture_pixels(
+ WGPUInstance instance,
+ WGPUDevice device,
+ WGPUTexture texture,
+ int width,
+ int height);
+
+#endif // !defined(STRIP_ALL)