summaryrefslogtreecommitdiff
path: root/src/tests/offscreen_render_target.cc
blob: 81ad08243a18b92d948e39f2fdbcd144c4e25602 (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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// 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 <cassert>
#include <cstdio>
#include <cstring>

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<uint32_t>(width), static_cast<uint32_t>(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<MapState*>(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<uint8_t> OffscreenRenderTarget::read_pixels() {
  const size_t buffer_size = width_ * height_ * 4;  // BGRA8
  std::vector<uint8_t> 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<uint32_t>(width_ * 4),
              .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);
  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<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
  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<const uint8_t*>(
      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) */