13#if defined(__EMSCRIPTEN__)
14#include <emscripten/emscripten.h>
17void Graphics::recordPresentedReadback(wgpu::CommandEncoder &encoder,
const wgpu::Texture &surfaceTexture) {
18 if (!surfaceCanCopySrc)
throw Exception(
"WebGPU: surface does not support screen readback");
19 const int w =
static_cast<int>(surfaceTexture.GetWidth());
20 const int h =
static_cast<int>(surfaceTexture.GetHeight());
21 if (!presentedReadback || presentedReadbackW !=
w || presentedReadbackH !=
h ||
22 presentedReadbackFormat != surfaceFormat) {
23 WGPUTextureDescriptor
desc{};
24 desc.label =
sv(
"eve_presented_readback");
25 desc.dimension = WGPUTextureDimension_2D;
26 desc.size = {
static_cast<uint32_t
>(
w),
static_cast<uint32_t
>(
h), 1};
27 desc.format = surfaceFormat;
28 desc.mipLevelCount = 1;
30 desc.usage = WGPUTextureUsage_CopyDst | WGPUTextureUsage_CopySrc;
31 presentedReadback = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor *
>(&
desc));
32 presentedReadbackW =
w;
33 presentedReadbackH =
h;
34 presentedReadbackFormat = surfaceFormat;
36 WGPUTexelCopyTextureInfo
from{},
to{};
37 from.texture = surfaceTexture.Get();
38 from.aspect = WGPUTextureAspect_All;
39 to.texture = presentedReadback.Get();
40 to.aspect = WGPUTextureAspect_All;
41 WGPUExtent3D extent{
static_cast<uint32_t
>(
w),
static_cast<uint32_t
>(
h), 1};
42 encoder.CopyTextureToTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo *
>(&
from),
43 reinterpret_cast<const wgpu::TexelCopyTextureInfo *
>(&
to),
44 reinterpret_cast<const wgpu::Extent3D *
>(&extent));
67bool copyTextureToCpu(wgpu::Instance &instance, wgpu::Device &
device, wgpu::Queue &queue, wgpu::Texture src,
int width,
68 int height, std::vector<uint8_t> &outRgba,
int bytesPerPixel = 4) {
69 if (!src)
return false;
70 uint64_t bytesPerRow =
static_cast<uint64_t
>(
width) * bytesPerPixel;
71 bytesPerRow = (bytesPerRow + 255) / 256 * 256;
74 WGPUBufferDescriptor bd{};
75 bd.label =
sv(
"eve_readback");
77 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
78 bd.mappedAtCreation =
false;
79 wgpu::Buffer dst =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor *
>(&bd));
81 wgpu::CommandEncoder enc =
device.CreateCommandEncoder();
82 WGPUTexelCopyTextureInfo
from{};
83 from.texture = src.Get();
85 from.aspect = WGPUTextureAspect_All;
86 from.origin = {0, 0, 0};
87 WGPUTexelCopyBufferInfo
to{};
88 to.buffer = dst.Get();
90 to.layout.bytesPerRow =
static_cast<uint32_t
>(bytesPerRow);
91 to.layout.rowsPerImage =
static_cast<uint32_t
>(
height);
92 WGPUExtent3D extent{
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
93 enc.CopyTextureToBuffer(
reinterpret_cast<const wgpu::TexelCopyTextureInfo *
>(&
from),
94 reinterpret_cast<const wgpu::TexelCopyBufferInfo *
>(&
to),
95 reinterpret_cast<const wgpu::Extent3D *
>(&extent));
96 wgpu::CommandBuffer cmd = enc.Finish();
97 queue.Submit(1, &cmd);
101 bool success =
false;
102 WGPUMapAsyncStatus
status = WGPUMapAsyncStatus_Force32;
104 WGPUBufferMapCallbackInfo cbInfo{};
105#if defined(__EMSCRIPTEN__)
106 cbInfo.mode = WGPUCallbackMode_AllowProcessEvents;
108 cbInfo.mode = WGPUCallbackMode_WaitAnyOnly;
110 cbInfo.callback = [](WGPUMapAsyncStatus
status, WGPUStringView ,
void *userdata1,
void * ) {
111 auto *
state =
static_cast<MapState *
>(userdata1);
113 state->success =
status == WGPUMapAsyncStatus_Success;
116 cbInfo.userdata1 = ↦
117 WGPUFuture
future = wgpuBufferMapAsync(dst.Get(), WGPUMapMode_Read, 0,
size, cbInfo);
119#if defined(__EMSCRIPTEN__)
121 while (!map.done && guard < 2000) {
123 wgpuInstanceProcessEvents(instance.Get());
127 WGPUFutureWaitInfo waitInfo{};
129 (void)wgpuInstanceWaitAny(instance.Get(), 1, &waitInfo, UINT64_MAX);
132 std::fprintf(stderr,
"[webgpu] texture readback map failed: status=%d done=%d\n",
int(map.status),
137 const uint8_t *data =
static_cast<const uint8_t *
>(dst.GetConstMappedRange(0,
size));
138 if (!data)
return false;
139 const size_t tightRowBytes =
static_cast<size_t>(
width) * bytesPerPixel;
140 outRgba.resize(tightRowBytes *
height);
142 std::memcpy(outRgba.data() + size_t(
y) * tightRowBytes, data + size_t(
y) * bytesPerRow, tightRowBytes);
147float decodeHalf(uint16_t
value) {
148 const uint32_t sign = uint32_t(
value & 0x8000u) << 16u;
149 uint32_t exponent = (
value >> 10u) & 0x1fu;
150 uint32_t mantissa =
value & 0x03ffu;
157 while ((mantissa & 0x0400u) == 0) {
162 bits = sign | ((exponent + 112u) << 23u) | (mantissa << 13u);
164 }
else if (exponent == 31u) {
165 bits = sign | 0x7f800000u | (mantissa << 13u);
167 bits = sign | ((exponent + 112u) << 23u) | (mantissa << 13u);
170 std::memcpy(&result, &bits,
sizeof(result));
174float hdrToDisplay(
float value) {
175 const float linear = std::max(
value, 0.f);
177 std::clamp((linear * (2.51f * linear + 0.03f)) / (linear * (2.43f * linear + 0.59f) + 0.14f), 0.f, 1.f);
178 return mapped <= 0.0031308f ? mapped * 12.92f : 1.055f * std::pow(mapped, 1.f / 2.4f) - 0.055f;
181Color hdrPixelToColor(
const uint8_t *pixel) {
185 hdrToDisplay(decodeHalf(
channels[2])), std::clamp(decodeHalf(
channels[3]), 0.f, 1.f));
192 const bool bgra = presented && (presentedReadbackFormat == WGPUTextureFormat_BGRA8Unorm ||
193 presentedReadbackFormat == WGPUTextureFormat_BGRA8UnormSrgb);
194 int w = presented ? presentedReadbackW : canvas ? canvas->
getWidth() : (sceneColorWidth > 0 ? sceneColorWidth : 1);
195 int h = presented ? presentedReadbackH
197 : (sceneColorHeight > 0 ? sceneColorHeight : 1);
198 if (
x < 0 || y < 0 || x >=
w ||
y >=
h)
return Color(0.f, 0.f, 0.f, 0.f);
199 std::vector<uint8_t> rgba;
200 wgpu::Texture src = presented ? presentedReadback
201 : canvas ? canvas->color
202 : (sceneColorSlots.empty() ? nullptr : sceneColorSlots[lastPresentSlot].color);
203 const bool hdrScene = !presented && canvas ==
nullptr && sceneColorFormat == WGPUTextureFormat_RGBA16Float;
204 if (!src || !copyTextureToCpu(instance,
device, queue, src,
w,
h, rgba, hdrScene ? 8 : 4))
return clearColor;
205 if (hdrScene)
return hdrPixelToColor(rgba.data() + (
size_t(
y) *
w +
x) * 8);
206 const uint8_t *
p = rgba.data() + (size_t(
y) *
w +
x) * 4;
207 return Color(
p[bgra ? 2 : 0] / 255.f,
p[1] / 255.f,
p[bgra ? 0 : 2] / 255.f,
p[3] / 255.f);
212 const bool bgra = presented && (presentedReadbackFormat == WGPUTextureFormat_BGRA8Unorm ||
213 presentedReadbackFormat == WGPUTextureFormat_BGRA8UnormSrgb);
214 int w = presented ? presentedReadbackW : canvas ? canvas->
getWidth() : (sceneColorWidth > 0 ? sceneColorWidth : 1);
215 int h = presented ? presentedReadbackH
217 : (sceneColorHeight > 0 ? sceneColorHeight : 1);
219 std::vector<uint8_t> rgba;
220 wgpu::Texture src = presented ? presentedReadback
221 : canvas ? canvas->color
222 : (sceneColorSlots.empty() ? nullptr : sceneColorSlots[lastPresentSlot].color);
223 const bool hdrScene = !presented && canvas ==
nullptr && sceneColorFormat == WGPUTextureFormat_RGBA16Float;
224 if (src && copyTextureToCpu(instance,
device, queue, src,
w,
h, rgba, hdrScene ? 8 : 4)) {
227 for (
size_t pixel = 0; pixel < size_t(
w) *
h; ++pixel) std::swap(rgba[pixel * 4], rgba[pixel * 4 + 2]);
228 std::memcpy(img->getData(), rgba.data(), rgba.size());
230 auto *dst =
static_cast<uint8_t *
>(img->getData());
231 for (
size_t pixel = 0; pixel < size_t(
w) *
h; ++pixel) {
232 const Color color = hdrPixelToColor(rgba.data() + pixel * 8);
233 dst[pixel * 4 + 0] = uint8_t(std::clamp(
color.r * 255.f, 0.f, 255.f));
234 dst[pixel * 4 + 1] = uint8_t(std::clamp(
color.g * 255.f, 0.f, 255.f));
235 dst[pixel * 4 + 2] = uint8_t(std::clamp(
color.b * 255.f, 0.f, 255.f));
236 dst[pixel * 4 + 3] = uint8_t(std::clamp(
color.a * 255.f, 0.f, 255.f));
244 if (!canvas || !canvas->hdr)
return nullptr;
247 std::vector<uint8_t> rgba16f;
248 if (!copyTextureToCpu(instance,
device, queue, canvas->color,
w,
h, rgba16f, 8))
return nullptr;
250 std::memcpy(img->getData(), rgba16f.data(), rgba16f.size());
255 submitPendingDeferredPasses();
256 if (!
device || gbufferSlots.empty() || gbufferWidth <= 0 || gbufferHeight <= 0)
return nullptr;
257 GbufferSlot &slot = gbufferSlots[std::min<size_t>(lastGbufferSlot, gbufferSlots.size() - 1)];
259 if (attachment ==
"depth")
260 src = slot.depthColor;
261 else if (attachment ==
"normal")
263 else if (attachment ==
"albedo")
265 else if (attachment ==
"pbrParams")
266 src = slot.pbrParams;
267 else if (attachment ==
"emissive")
272 std::vector<uint8_t> rgba;
273 if (!copyTextureToCpu(instance,
device, queue, src, gbufferWidth, gbufferHeight, rgba))
return nullptr;
275 std::memcpy(
image->getData(), rgba.data(), rgba.size());
276 if (attachment ==
"depth") {
278 auto*
pixels =
static_cast<uint8_t*
>(
image->getData());
279 const size_t pixelCount = size_t(gbufferWidth) * size_t(gbufferHeight);
280 for (
size_t i = 0; i < pixelCount; ++i) {
289 submitPendingDeferredPasses();
290 if (!
device || decalSlots.empty() || decalWidth <= 0 || decalHeight <= 0)
return nullptr;
291 DecalSlot &slot = decalSlots[std::min<size_t>(lastDecalSlot, decalSlots.size() - 1)];
293 if (attachment ==
"albedo")
295 else if (attachment ==
"normal")
297 else if (attachment ==
"params")
301 std::vector<uint8_t> rgba;
302 if (!copyTextureToCpu(instance,
device, queue, src, decalWidth, decalHeight, rgba))
return nullptr;
304 std::memcpy(
image->getData(), rgba.data(), rgba.size());
314bool encodeRgbaToPng(
const std::string &
path,
int width,
int height,
const std::vector<uint8_t> &rgba) {
317 std::memcpy(img.getData(), rgba.data(), rgba.size());
318 std::unique_ptr<eve::filesystem::FileData> png(
319 img.encode(medialoader::FormatHandler::ENCODED_PNG,
path.c_str(),
false));
320 if (!png)
return false;
321 std::ofstream out(
path, std::ios::binary);
322 if (!out)
return false;
323 out.write(
static_cast<const char *
>(png->getData()),
static_cast<std::streamsize
>(png->getSize()));
334 if ((pendingReadback_ && !pendingReadback_->done) || !
device)
return false;
336 wgpu::Texture src = presented ? presentedReadback : lastReadbackTex;
337 int w = presented ? presentedReadbackW : lastReadbackW;
338 int h = presented ? presentedReadbackH : lastReadbackH;
339 if (!src ||
w <= 0 ||
h <= 0) {
341 if (sceneColorSlots.empty())
return false;
342 const uint32_t slot = lastPresentSlot;
343 if (slot >= sceneColorSlots.size())
return false;
344 src = sceneColorSlots[slot].color;
346 h = sceneColorHeight;
347 if (!src ||
w <= 0 ||
h <= 0)
return false;
350 const bool hdr = src.GetFormat() == wgpu::TextureFormat::RGBA16Float;
352 src.GetFormat() == wgpu::TextureFormat::BGRA8Unorm || src.GetFormat() == wgpu::TextureFormat::BGRA8UnormSrgb;
353 uint64_t bytesPerRow =
static_cast<uint64_t
>(
w * (hdr ? 8 : 4));
354 bytesPerRow = (bytesPerRow + 255) / 256 * 256;
355 const uint64_t
size = bytesPerRow *
static_cast<uint64_t
>(
h);
357 WGPUBufferDescriptor bd{};
358 bd.label =
sv(
"eve_readback");
360 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
361 bd.mappedAtCreation =
false;
362 wgpu::Buffer dst =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor *
>(&bd));
363 if (!dst)
return false;
368 wgpu::CommandEncoder enc =
device.CreateCommandEncoder();
369 WGPUTexelCopyTextureInfo
from{};
370 from.texture = src.Get();
372 from.aspect = WGPUTextureAspect_All;
373 from.origin = {0, 0, 0};
374 WGPUTexelCopyBufferInfo
to{};
375 to.buffer = dst.Get();
376 to.layout.offset = 0;
377 to.layout.bytesPerRow =
static_cast<uint32_t
>(bytesPerRow);
378 to.layout.rowsPerImage =
static_cast<uint32_t
>(
h);
379 WGPUExtent3D extent{
static_cast<uint32_t
>(
w),
static_cast<uint32_t
>(
h), 1};
380 enc.CopyTextureToBuffer(
reinterpret_cast<const wgpu::TexelCopyTextureInfo *
>(&
from),
381 reinterpret_cast<const wgpu::TexelCopyBufferInfo *
>(&
to),
382 reinterpret_cast<const wgpu::Extent3D *
>(&extent));
383 wgpu::CommandBuffer cmd = enc.Finish();
384 queue.Submit(1, &cmd);
386 auto pr = std::make_unique<PendingReadback>();
390 pr->bytesPerRow = bytesPerRow;
395 WGPUBufferMapCallbackInfo cbInfo{};
396 cbInfo.mode = WGPUCallbackMode_AllowProcessEvents;
397 cbInfo.callback = [](WGPUMapAsyncStatus
status, WGPUStringView ,
void *userdata1,
void * ) {
398 auto *
p =
static_cast<PendingReadback *
>(userdata1);
399 p->mapped = (
status == WGPUMapAsyncStatus_Success);
401 cbInfo.userdata1 = pr.get();
402 wgpuBufferMapAsync(dst.Get(), WGPUMapMode_Read, 0,
size, cbInfo);
404 pendingReadback_ = std::move(pr);
409 if (!pendingReadback_)
return 0;
410 if (!pendingReadback_->done)
return 1;
411 return pendingReadback_->ok ? 2 : 3;
415 if (!pendingReadback_ || pendingReadback_->done)
return;
416 auto &pr = *pendingReadback_;
421#if defined(__EMSCRIPTEN__)
422 wgpuInstanceProcessEvents(instance.Get());
426 const uint8_t *data =
427 static_cast<const uint8_t *
>(pr.dst.GetConstMappedRange(0, pr.bytesPerRow *
static_cast<uint64_t
>(pr.height)));
428 std::vector<uint8_t> rgba(
static_cast<size_t>(pr.width) * pr.height * 4);
430 for (
int y = 0;
y < pr.height; ++
y) {
431 auto *dst = rgba.data() + size_t(
y) * pr.width * 4;
432 const auto *row = data + size_t(
y) * pr.bytesPerRow;
433 for (
int x = 0;
x < pr.width; ++
x) {
435 const auto color = hdrPixelToColor(row +
size_t(
x) * 8);
436 dst[
x * 4] = uint8_t(std::clamp(
color.r * 255.f, 0.f, 255.f));
437 dst[
x * 4 + 1] = uint8_t(std::clamp(
color.g * 255.f, 0.f, 255.f));
438 dst[
x * 4 + 2] = uint8_t(std::clamp(
color.b * 255.f, 0.f, 255.f));
439 dst[
x * 4 + 3] = uint8_t(std::clamp(
color.a * 255.f, 0.f, 255.f));
441 dst[
x * 4] = row[
x * 4 + (pr.bgra ? 2 : 0)];
442 dst[
x * 4 + 1] = row[
x * 4 + 1];
443 dst[
x * 4 + 2] = row[
x * 4 + (pr.bgra ? 0 : 2)];
444 dst[
x * 4 + 3] = row[
x * 4 + 3];
450 pr.ok = data && encodeRgbaToPng(pr.path, pr.width, pr.height, rgba);
std::map< std::string, std::vector< Key >, std::less<> > channels
wgpu::PopErrorScopeStatus status
HexCoordinates to
Cell the unit walks towards on this segment.
bool screenReadbackEnabled
image::ImageData * readGBufferToImageData(const std::string &attachment) override
Reads g buffer to image data.
void pumpReadback()
Advances a pending frame readback; called every present().
Color getPixelImpl(OffscreenCanvas *canvas, int x, int y)
Blocking CPU readback of an offscreen canvas or scene color target.
image::ImageData * newImageData() override
Creates a image data. @ownership Caller deletes unless documented otherwise.
image::ImageData * newHDRImageDataImpl(OffscreenCanvas *canvas)
Blocking linear RGBA16F readback of an HDR offscreen canvas. @ownership The caller owns the returned ...
bool beginFrameReadback(const std::string &path) override
Begins frame readback.
image::ImageData * newImageDataImpl(OffscreenCanvas *canvas)
Creates a image data impl. @ownership Caller deletes unless documented otherwise.
int frameReadbackStatus() const override
Frame readback status.
Color getPixel(int x, int y) override
Returns the pixel.
image::ImageData * readDecalLayerToImageData(const std::string &attachment) override
Reads decal layer to image data.
Offscreen render target (RGBA8Unorm color, optional Depth32Float). 2D batches are flushed into the ca...
int getWidth() const override
Returns the width.
int getHeight() const override
Returns the height.
Represents raw pixel data.
WGPUStringView sv(const char *s)
Build a WGPUStringView from a C string (null-safe, length auto-computed).
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
std::future< vkb::Instance > future