1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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, ©_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)
|