Skip to content
37 changes: 37 additions & 0 deletions config/seeds/5343000F.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"start": "0x0002C1AE",
"observed": true,
"note": "PsCreateSystemThreadEx StartContext1 observed at runtime; decodes as a function body."
},
{
"start": "0x000B86E0",
"observed": true,
"note": "PsCreateSystemThreadEx StartContext1 observed at runtime; decodes as a function body."
},
{
"start": "0x001DF280",
"observed": true,
"note": "Indirect-call target observed at runtime; decodes as a function body."
},
{
"start": "0x0023A0F0",
"observed": true,
"note": "PsCreateSystemThreadEx StartContext1 observed at runtime; decodes as a function body."
},
{
"start": "0x0024AED0",
"observed": true,
"note": "PsCreateSystemThreadEx StartContext1 observed at runtime; decodes as a function body."
},
{
"start": "0x002DE7B0",
"observed": true,
"note": "PsCreateSystemThreadEx StartContext1 observed at runtime; decodes as a function body."
},
{
"start": "0x002EA576",
"observed": true,
"note": "Indirect-call target observed at runtime; decodes as a function body."
}
]
17 changes: 17 additions & 0 deletions src/d3d/rhi.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,23 @@ int rhi_image_copy(RhiImage *dst, RhiImage *src)
{
return g_rhi->image_copy ? g_rhi->image_copy(dst, src) : -1;
}

int rhi_occlusion_begin(void)
{
return g_rhi->occlusion_begin ? g_rhi->occlusion_begin() : -1;
}

void rhi_occlusion_end(RhiOcclusion *out)
{
memset(out, 0, sizeof *out);
if (g_rhi->occlusion_end)
g_rhi->occlusion_end(out);
}

int rhi_occlusion_result(const RhiOcclusion *t, uint64_t *samples)
{
return g_rhi->occlusion_result ? g_rhi->occlusion_result(t, samples) : -1;
}
int rhi_image_readback(RhiImage *img, uint32_t sub, void *dst, uint32_t dst_pitch)
{
return g_rhi->image_readback(img, sub, dst, dst_pitch);
Expand Down
17 changes: 17 additions & 0 deletions src/d3d/rhi.h
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,23 @@ void rhi_draw_indexed(uint32_t index_count, uint32_t first_index, int32_t base_v
void rhi_clear_color(RhiView *v, const float rgba[4]);
void rhi_clear_depth(RhiView *v, uint32_t flags, float depth, uint8_t stencil);

/* ---- occlusion counting --------------------------------------------------------- */

/* Samples that pass the depth and stencil tests, counted over the draws made
* between rhi_occlusion_begin and rhi_occlusion_end: what an Xbox visibility
* test reports (D3DDevice_BeginVisibilityTest .. EndVisibilityTest). Each draw
* is counted by a query of its own, so a test may span render-target changes.
* rhi_occlusion_result waits for the GPU and returns 0 with the total in
* *samples, or -1 when the backend cannot count or the test's queries have
* been reused since (a result asked for long after its frame). */
typedef struct RhiOcclusion {
uint32_t first, count, generation;
} RhiOcclusion;

int rhi_occlusion_begin(void); /* 0 when counting has started */
void rhi_occlusion_end(RhiOcclusion *out);
int rhi_occlusion_result(const RhiOcclusion *t, uint64_t *samples);

/* ---- backends ----------------------------------------------------------------- */

/* For a test that makes its own D3D11 device: hand it to the backend in
Expand Down
4 changes: 4 additions & 0 deletions src/d3d/rhi_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ typedef struct RhiBackend {
/* Last, so a backend's positional initialiser that predates it leaves it
* NULL, which rhi_image_copy reports as unsupported. */
int (*image_copy)(RhiImage *dst, RhiImage *src);
/* After image_copy for the same reason: NULL is "cannot count". */
int (*occlusion_begin)(void);
void (*occlusion_end)(RhiOcclusion *);
int (*occlusion_result)(const RhiOcclusion *, uint64_t *);
} RhiBackend;

extern const RhiBackend rhi_d3d11_backend;
Expand Down
72 changes: 72 additions & 0 deletions src/d3d/rhi_d3d11.c
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,11 @@ static int d_device_create(const RhiDeviceDesc *dd)
return 0;
}

static void occl_release(void);

static void d_device_destroy(void)
{
occl_release();
staging_release();
release_back_buffer_view();
if (g_swap) { IDXGISwapChain_Release(g_swap); g_swap = NULL; }
Expand Down Expand Up @@ -1291,6 +1294,74 @@ static void d_clear_depth(RhiView *v, uint32_t flags, float z, uint8_t s)
ID3D11DeviceContext_ClearDepthStencilView(g_ctx, (ID3D11DepthStencilView *)v->v, flags, z, s);
}

/* Occlusion counting (rhi_occlusion_*): one D3D11 occlusion query a test,
* which may span anything, from a ring; a slot's use count is the test's
* generation, so a test answered after its slot came round again reads as
* reused. */
#define OCCL_SLOTS 256u
static ID3D11Query *g_occl[OCCL_SLOTS];
static uint32_t g_occl_uses[OCCL_SLOTS];
static uint32_t g_occl_next;
static int g_occl_open = -1;

static void occl_release(void)
{
uint32_t i;
for (i = 0; i < OCCL_SLOTS; i++)
if (g_occl[i]) { ID3D11Query_Release(g_occl[i]); g_occl[i] = NULL; }
g_occl_open = -1;
}

static int d_occlusion_begin(void)
{
uint32_t i = g_occl_next % OCCL_SLOTS;

if (!g_dev || !g_ctx)
return -1;
if (!g_occl[i]) {
D3D11_QUERY_DESC qd;
memset(&qd, 0, sizeof qd);
qd.Query = D3D11_QUERY_OCCLUSION;
if (FAILED(ID3D11Device_CreateQuery(g_dev, &qd, &g_occl[i])))
return -1;
}
g_occl_next++;
g_occl_uses[i]++;
ID3D11DeviceContext_Begin(g_ctx, (ID3D11Asynchronous *)g_occl[i]);
g_occl_open = (int)i;
return 0;
}

static void d_occlusion_end(RhiOcclusion *out)
{
if (g_occl_open < 0)
return;
ID3D11DeviceContext_End(g_ctx, (ID3D11Asynchronous *)g_occl[g_occl_open]);
out->first = (uint32_t)g_occl_open;
out->count = 1;
out->generation = g_occl_uses[g_occl_open];
g_occl_open = -1;
}

static int d_occlusion_result(const RhiOcclusion *t, uint64_t *samples)
{
UINT64 n = 0;
HRESULT hr;

*samples = 0;
if (!t->count)
return 0;
if (t->first >= OCCL_SLOTS || !g_occl[t->first] || g_occl_uses[t->first] != t->generation)
return -1;
while ((hr = ID3D11DeviceContext_GetData(g_ctx, (ID3D11Asynchronous *)g_occl[t->first],
&n, sizeof n, 0)) == S_FALSE)
;
if (FAILED(hr))
return -1;
*samples = n;
return 0;
}

const RhiBackend rhi_d3d11_backend = {
"d3d11",
d_device_create, d_device_destroy, d_device_ready, d_swapchain_view, d_swapchain_resize,
Expand All @@ -1307,6 +1378,7 @@ const RhiBackend rhi_d3d11_backend = {
d_set_uniform_buffers, d_set_textures, d_set_samplers, d_set_blend_state, d_set_depth_state,
d_set_raster_state, d_draw, d_draw_indexed, d_clear_color, d_clear_depth,
d_image_copy,
d_occlusion_begin, d_occlusion_end, d_occlusion_result,
};

#endif /* _WIN32 */
114 changes: 113 additions & 1 deletion src/d3d/rhi_vulkan.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ int rhi_vk_dxc_compile(uint32_t stage, const RhiShaderSource *src,
#define RING_CHUNK (8u << 20)
#define MAX_VB 16
#define MAX_TEX 16
#define OCCL_QUERIES 8192u /* rhi_occlusion_*: one a counted draw */
#define MAX_UBO 8
#define MAX_REFL 32

Expand Down Expand Up @@ -241,6 +242,7 @@ static struct {
VmaAllocator vma;
VkFormat depth24; /* D24S8, or D32S8 where there is none */
int feat_depth_clamp, feat_fill, feat_bias_clamp, feat_aniso, feat_border, feat_lod_bias;
int feat_query_reset, feat_precise;
int validation;

VkDescriptorSetLayout set_layout;
Expand Down Expand Up @@ -317,6 +319,13 @@ static struct {
uint32_t read_mask, write_mask, ref;
float blend_const[4];
} dyn;

/* Occlusion counting (rhi_occlusion_*): one query a draw while a test is
* open, taken in order from the pool and reset from the host when the
* pool is used up. A generation tells a test whose queries were reused. */
VkQueryPool occl_pool;
uint32_t occl_next, occl_first, occl_gen;
int occl_counting, occl_overflow;
} V;

static struct {
Expand Down Expand Up @@ -1414,6 +1423,11 @@ static int create_device(void)
f2.features.depthClamp = V.feat_depth_clamp = have.features.depthClamp;
f2.features.depthBiasClamp = V.feat_bias_clamp = have.features.depthBiasClamp;
f2.features.samplerAnisotropy = V.feat_aniso = have.features.samplerAnisotropy;
/* Visibility tests (rhi_occlusion_*): the NV2A counts every sample that
* passes, so a precise count where the device has one; the pool is reset
* from the host, between frames' worth of draws. */
f2.features.occlusionQueryPrecise = V.feat_precise = have.features.occlusionQueryPrecise;
f12.hostQueryReset = V.feat_query_reset = have12.hostQueryReset;
f12.timelineSemaphore = VK_TRUE;
f12.scalarBlockLayout = have12.scalarBlockLayout;
f12.samplerMirrorClampToEdge = have12.samplerMirrorClampToEdge;
Expand Down Expand Up @@ -1637,6 +1651,18 @@ static int create_frames(void)
VKCHECK(vkAllocateCommandBuffers(V.dev, &ai, &V.frames[i].cmd), "command buffer");
VKCHECK(vkCreateSemaphore(V.dev, &sci, NULL, &V.frames[i].acquire), "semaphore");
}
if (V.feat_query_reset) {
VkQueryPoolCreateInfo qci = { VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO };
qci.queryType = VK_QUERY_TYPE_OCCLUSION;
qci.queryCount = OCCL_QUERIES;
if (vkCreateQueryPool(V.dev, &qci, NULL, &V.occl_pool) == VK_SUCCESS)
vkResetQueryPool(V.dev, V.occl_pool, 0, OCCL_QUERIES);
else
V.occl_pool = VK_NULL_HANDLE;
}
if (!V.occl_pool)
fprintf(stderr, "[RHI] vulkan: no host query reset on %s; visibility tests are not "
"counted\n", V.props.deviceName);
return 0;
}

Expand Down Expand Up @@ -1922,6 +1948,7 @@ static void v_device_destroy(void)
destroy_swapchain_views();
if (SC.sc) vkDestroySwapchainKHR(V.dev, SC.sc, NULL);
vkDestroySemaphore(V.dev, V.timeline, NULL);
if (V.occl_pool) vkDestroyQueryPool(V.dev, V.occl_pool, NULL);
pcache_save();
vkDestroyPipelineCache(V.dev, V.pcache, NULL);
vkDestroyPipelineLayout(V.dev, V.pipe_layout, NULL);
Expand Down Expand Up @@ -3969,22 +3996,106 @@ static int prepare_draw(void)
return push_descriptors(p);
}

/* A draw inside an open visibility test runs inside a query of its own:
* a query begun in a rendering scope must end in it, and a test can span a
* render-target change, which ends the scope. 1 when this draw is counted. */
static int occl_draw_begin(void)
{
if (!V.occl_counting)
return 0;
if (V.occl_next >= OCCL_QUERIES) {
V.occl_overflow = 1;
return 0;
}
vkCmdBeginQuery(cmd(), V.occl_pool, V.occl_next,
V.feat_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
return 1;
}

static void occl_draw_end(int counted)
{
if (counted)
vkCmdEndQuery(cmd(), V.occl_pool, V.occl_next++);
}

static void v_draw(uint32_t n, uint32_t first)
{
if (n && prepare_draw())
int q;
if (n && prepare_draw()) {
q = occl_draw_begin();
vkCmdDraw(cmd(), n, 1, first, 0);
occl_draw_end(q);
}
}

static void v_draw_indexed(uint32_t n, uint32_t first, int32_t base)
{
BufVer *v;
int q;
if (!n || !V.ib || !V.ib->nver || !prepare_draw())
return;
v = &V.ib->ver[V.ib->cur];
v->used = V.submitted + 1;
vkCmdBindIndexBuffer(cmd(), v->b, V.ib_offset,
V.ib_bits == 32 ? VK_INDEX_TYPE_UINT32 : VK_INDEX_TYPE_UINT16);
q = occl_draw_begin();
vkCmdDrawIndexed(cmd(), n, 1, first, base, 0);
occl_draw_end(q);
}

static int v_occlusion_begin(void)
{
if (!V.occl_pool)
return -1;
/* Room for a test's draws, or start the pool again: everything recorded
* is finished first, so no query being reset is still in flight. A test
* still waiting for its answer from before then reads as reused. */
if (V.occl_next > OCCL_QUERIES - OCCL_QUERIES / 8) {
flush_wait();
vkResetQueryPool(V.dev, V.occl_pool, 0, OCCL_QUERIES);
V.occl_next = 0;
V.occl_gen++;
}
V.occl_counting = 1;
V.occl_overflow = 0;
V.occl_first = V.occl_next;
return 0;
}

static void v_occlusion_end(RhiOcclusion *out)
{
if (!V.occl_counting)
return;
out->first = V.occl_first;
out->count = V.occl_next - V.occl_first;
out->generation = V.occl_overflow ? ~V.occl_gen : V.occl_gen;
V.occl_counting = 0;
}

static int v_occlusion_result(const RhiOcclusion *t, uint64_t *samples)
{
uint64_t r[256];
uint32_t done = 0;

*samples = 0;
if (!V.occl_pool || t->generation != V.occl_gen)
return -1;
if (!t->count)
return 0;
flush_wait();
while (done < t->count) {
uint32_t n = t->count - done, k;
if (n > 256)
n = 256;
if (vkGetQueryPoolResults(V.dev, V.occl_pool, t->first + done, n, sizeof r, r,
sizeof r[0], VK_QUERY_RESULT_64_BIT |
VK_QUERY_RESULT_WAIT_BIT) != VK_SUCCESS)
return -1;
for (k = 0; k < n; k++)
*samples += r[k];
done += n;
}
return 0;
}

const RhiBackend rhi_vulkan_backend = {
Expand All @@ -4003,6 +4114,7 @@ const RhiBackend rhi_vulkan_backend = {
v_set_uniform_buffers, v_set_textures, v_set_samplers, v_set_blend_state, v_set_depth_state,
v_set_raster_state, v_draw, v_draw_indexed, v_clear_color, v_clear_depth,
v_image_copy,
v_occlusion_begin, v_occlusion_end, v_occlusion_result,
};

#endif /* RHI_HAVE_VULKAN */
Loading
Loading