Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions include/gfx/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace gfx::detail {

inline constexpr uint32_t kFramesInFlight = 2;
inline constexpr uint32_t kFramesInFlight = 4;
// Descriptor slots in the bindless array; a handle's low 16 bits are its slot.
inline constexpr uint32_t kMaxTextures = 8192;
inline constexpr uint32_t kBaseSamplerCount = 8;
Expand Down Expand Up @@ -131,14 +131,10 @@ struct TransientSpan {
};

struct DepthQuery {
LogicalRect rect = {};
bool queued = false;
bool recorded = false;
bool valid = false;
float result = 0.0f;
VkDeviceSize offset = 0;
uint32_t texels = 0;
uint8_t *data = nullptr;
LogicalRect rect = {};
bool queued = false;
bool valid = false;
float result = 0.0f;
};

struct Frame {
Expand All @@ -154,6 +150,7 @@ struct Frame {
std::vector<std::function<void()>> deletions;
// The only buffer the GPU writes for the host; kept apart from the transients, which it reads.
Buffer depth_readback;
std::array<uint32_t, kDepthQueryCount> depth_texels = {};
};

struct Swapchain {
Expand Down Expand Up @@ -312,7 +309,6 @@ struct Context {
BoundState bound;

std::array<DepthQuery, kDepthQueryCount> depth_queries;
bool depth_queries_recorded = false;

// Display lists. The canonical render of a list draws into main_color like an immediate frame;
// a display render draws into display_color with display_depth.
Expand Down Expand Up @@ -432,6 +428,6 @@ LogicalRect ExtendRect(LogicalRect rect, uint32_t axes, const LogicalRect &frame

// readback.cpp
void RecordDepthQueries(VkCommandBuffer cmd);
void HarvestDepthQueries();
void HarvestDepthQueries(Frame &frame);

} // namespace gfx::detail
4 changes: 2 additions & 2 deletions include/gfx/gfx.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,9 +625,9 @@ bool SnapshotFrame(TextureHandle dst);
// ---- Readback --------------------------------------------------------------------------------

// Queues a read of the main depth buffer over a logical rect, taken at EndFrame. The result is
// the farthest (smallest) depth in it, 1 near and 0 far, available after EndFrame.
// the farthest (smallest) depth in it, 1 near and 0 far, available a frame or two later.
void ReadDepth(uint32_t id, float x, float y, float w = 1.0f, float h = 1.0f);
// The answer to the last ReadDepth(id); none if the rect was off the target or never asked.
// The newest answer to ReadDepth(id); none if the rect was off the target or none has landed.
std::optional<float> DepthResult(uint32_t id);
// The last presented frame, RGBA8 rows top to bottom. Outside a frame only.
bool ReadbackFrame(std::vector<uint8_t> &rgba, uint32_t &width, uint32_t &height);
Expand Down
10 changes: 6 additions & 4 deletions src/cpp/gfx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ else is internal. No game header is reachable from here: `platform/`, `gfx/` and
- `RendererInit(window, config)` creates the device, the swapchain, the main targets and every
pipeline (below). `config.pipeline_cache` defaults to `save/pipeline_cache.bin`;
`config.progress(done, total)` is called on the calling thread while pipelines compile.
- `BeginFrame()` / `EndFrame()` bracket a frame drawn as it is called. Two frames are in flight.
- `BeginFrame()` / `EndFrame()` bracket a frame drawn as it is called. Four frames are in flight:
a tick submits a canonical render and its display renders, so the GPU may run two ticks behind.
`BeginFrame` returns false when there is nothing to draw to (minimised); draws are then dropped and
`EndFrame` does nothing. Every frame starts on the main target. `EndFrame` presents. The game's
ticks are recorded instead and rendered from their display lists (below).
Expand Down Expand Up @@ -292,8 +293,9 @@ skinning), O-10 (the add step's alpha is 128 x vertex alpha squared), O-11 (the
- `ReadDepth(id, rect)` (16 ids) reads the main depth buffer at `EndFrame` (or at the end of a
list's canonical render), at most 64x64 pixels
around the rect's centre, and keeps the farthest (smallest) value: the game's pick-Z keeps the
farthest of an 8x8 block. `EndFrame` waits for that frame when a query was made, so
`DepthResult(id)` holds the answer as soon as `EndFrame` returns.
farthest of an 8x8 block. Nothing waits for it: the answer lands when the CPU next waits on
that frame's slot, so `DepthResult(id)` holds a frame or two late (a tick or two of the game's)
and keeps the last answer until then.
- `ReadbackFrame` (the last presented frame: a display render, else the newest main image) and
`ReadbackTexture` are synchronous and only allowed outside a frame or recording. `WritePng`
writes an RGB PNG with stored (uncompressed) deflate.
Expand All @@ -317,7 +319,7 @@ renders one.
screen).
- **Canonical render** (`options.canonical`): every entry runs, in order, into the next of the two
main colour images, which then becomes what `kPreviousFrame` samples and what a copy or snapshot
of `kMainTarget` reads; depth queries are answered from it before `RenderList` returns. It is the
of `kMainTarget` reads; depth queries read it (answered later, see Readback). It is the
frame `BeginFrame` / `EndFrame` would have drawn, pixel for pixel, and nothing is presented.
`PresentCanonical()` presents it as it is.
- **Display render**: only the drawing entries run, into a third colour image with its own depth
Expand Down
34 changes: 16 additions & 18 deletions src/cpp/gfx/readback.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,13 @@ void PutChunk(std::vector<uint8_t> &out, const char type[4], const std::vector<u
void RecordDepthQueries(VkCommandBuffer cmd) {
Image &depth = g.main_depth;
LogicalMapping mapping = MainMapping(depth.width, depth.height);
Frame &frame = CurrentFrame();
bool transition = false;
for (DepthQuery &query : g.depth_queries) {
if (!query.queued) {
continue;
}
query.queued = false;
query.recorded = false;
query.valid = false;
float x0 = std::floor(query.rect.x * mapping.scale_x + mapping.offset_x);
float y0 = std::floor(query.rect.y * mapping.scale_y + mapping.offset_y);
float x1 = std::ceil((query.rect.x + query.rect.w) * mapping.scale_x + mapping.offset_x);
Expand All @@ -106,6 +105,7 @@ void RecordDepthQueries(VkCommandBuffer cmd) {
x1 = std::min(x1, static_cast<float>(depth.width));
y1 = std::min(y1, static_cast<float>(depth.height));
if (x1 <= x0 || y1 <= y0) {
query.valid = false;
continue;
}
uint32_t width = std::min(static_cast<uint32_t>(x1 - x0), kMaxQuerySide);
Expand All @@ -115,58 +115,56 @@ void RecordDepthQueries(VkCommandBuffer cmd) {
x = std::clamp(x, 0, static_cast<int32_t>(depth.width - width));
y = std::clamp(y, 0, static_cast<int32_t>(depth.height - height));

Buffer &buffer = CurrentFrame().depth_readback;
Buffer &buffer = frame.depth_readback;
if (buffer.buffer == VK_NULL_HANDLE) {
buffer = CreateBuffer(kDepthQueryCount * kQueryBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT, true);
}
VkDeviceSize offset = static_cast<VkDeviceSize>(&query - g.depth_queries.data()) * kQueryBytes;
size_t index = static_cast<size_t>(&query - g.depth_queries.data());
if (!transition) {
Transition(cmd, depth, TransferSrc());
transition = true;
}
VkBufferImageCopy region = {};
region.bufferOffset = offset;
region.bufferOffset = index * kQueryBytes;
region.imageSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1};
region.imageOffset = {x, y, 0};
region.imageExtent = {width, height, 1};
vkCmdCopyImageToBuffer(cmd, depth.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer.buffer, 1,
&region);
query.data = buffer.memory.mapped + offset;
query.texels = width * height;
query.recorded = true;
frame.depth_texels[index] = width * height;
}
if (transition) {
HostBarrier(cmd);
ToRest(cmd, depth);
g.depth_queries_recorded = true;
}
}

void HarvestDepthQueries() {
for (DepthQuery &query : g.depth_queries) {
if (!query.recorded) {
void HarvestDepthQueries(Frame &frame) {
for (size_t index = 0; index < kDepthQueryCount; index++) {
uint32_t texels = frame.depth_texels[index];
if (texels == 0) {
continue;
}
frame.depth_texels[index] = 0;
const uint8_t *data = frame.depth_readback.memory.mapped + index * kQueryBytes;
// The depth aspect copies out as a float for D32S8 and as 24 bits in a word for D24S8.
bool unorm = g.depth_format == VK_FORMAT_D24_UNORM_S8_UINT;
float farthest = 1.0f;
for (uint32_t i = 0; i < query.texels; i++) {
for (uint32_t i = 0; i < texels; i++) {
float value;
if (unorm) {
uint32_t word;
std::memcpy(&word, query.data + i * sizeof(word), sizeof(word));
std::memcpy(&word, data + i * sizeof(word), sizeof(word));
value = static_cast<float>(word & 0xFFFFFF) / static_cast<float>(0xFFFFFF);
} else {
std::memcpy(&value, query.data + i * sizeof(float), sizeof(float));
std::memcpy(&value, data + i * sizeof(float), sizeof(float));
}
farthest = std::min(farthest, value);
}
DepthQuery &query = g.depth_queries[index];
query.result = farthest;
query.valid = true;
query.recorded = false;
query.data = nullptr;
}
g.depth_queries_recorded = false;
}

} // namespace detail
Expand Down
11 changes: 4 additions & 7 deletions src/cpp/gfx/renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ void WaitFrame(Frame &frame) {
if (frame.pending) {
Check(vkWaitForFences(g.device, 1, &frame.fence, VK_TRUE, UINT64_MAX), "vkWaitForFences");
frame.pending = false;
HarvestDepthQueries(frame);
}
}

Expand Down Expand Up @@ -909,11 +910,7 @@ bool OpenFrame() {
return true;
}

void FinishFrame(Frame &frame) {
if (g.depth_queries_recorded) {
WaitFrame(frame);
HarvestDepthQueries();
}
void FinishFrame() {
g.main_current ^= 1;
AdvanceSlot();
}
Expand Down Expand Up @@ -1057,7 +1054,7 @@ void CloseListFrame(bool canonical, bool present) {
if (canonical) {
RecordDepthQueries(cmd);
SubmitWithoutPresent(cmd, CurrentMainColor());
FinishFrame(frame);
FinishFrame();
g.canonical_newest = true;
g.presented_display = false;
return;
Expand Down Expand Up @@ -1111,7 +1108,7 @@ void EndFrame() {
} else {
PresentImage(frame, cmd, CurrentMainColor());
}
FinishFrame(frame);
FinishFrame();
}

bool PresentCanonical() {
Expand Down