summaryrefslogtreecommitdiff
path: root/src/gpu/texture_readback.cc
blob: 0eb63d74e434148e168a25d3e6d1470889ff92c0 (plain)
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
144
145
// 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);

  // Wait for copy to complete before mapping
  wgpuDevicePoll(device, true, nullptr);

  // 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_AllowProcessEvents;  // Fire during ProcessEvents
  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
    wgpuDevicePoll(device, true, nullptr);
#endif
  }

  if (!map_state.done || map_state.status != WGPUMapAsyncStatus_Success) {
    wgpuBufferRelease(staging);
    return pixels; // Return empty on timeout or failure
  }

  // 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)