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