Skip to content

Commit 64949e7

Browse files
committed
gfx/d3d9cg: Recover from shader compile failures instead of black-
screening Three closely-related issues stacked on top of each other made any single-pass-or-later compile failure inside a multi-pass preset permanently brick the driver until restart: 1. d3d9_cg_renderchain_add_pass discarded d3d9_cg_load_program's return value. When a pass like mdapt-pass3.cg failed to compile (C5109 domain-conflict, etc.), the pass was still appended to the chain with vprg/fprg = NULL. The render path then called cgD3D9BindProgram(NULL) every frame, which leaves the device in an unspecified state -- on the user's GPU, that's a black core frame with menu/widgets/overlay still drawing fine (those use fixed-function or stock-blend, not the renderchain). 2. d3d9_cg_load_program's error: label leaked the partially-created Cg program(s). In the cgD3D9LoadProgram failure case, pass->fprg was a valid Cg program object (compile succeeded, GPU-load did not), and any subsequent code path that didn't NULL-check would happily try to bind it. Worse, the leaked program objects pin the surrounding context until cgDestroyContext runs. 3. d3d9_cg_set_shader's failure handling left d3d->renderchain_data pointing at a half-built chain (whatever passes succeeded before the failure, plus the bad pass with NULL programs after #1). The driver kept rendering through that chain. Even reapplying the stock shader from the menu wouldn't recover, because the menu's "Apply Shader" path also goes through set_shader -> process_shader -> restore, hitting the same broken state if anything along the way faulted. Fix all three: - add_pass now memsets the local pass struct (so the error path has a well-defined struct to walk), checks load_program's return, and funnels every failure through a single error: label that releases the Cg programs, vertex declaration, vertex buffer, texture, and attrib_map it created before the failure point. - load_program's error: label cgDestroyProgram()s pass->vprg/fprg (whichever is non-NULL) and zeroes them, so callers that ignore the return value still see a clean pass struct. - set_shader, on process_shader-or-restore failure, frees d3d->shader_path, runs process_shader again to populate the stock defaults, and calls restore once more to rebuild the chain against the stock shader. The original return value (false) is preserved, so the menu still surfaces the failure to the user -- they just don't get stuck on a black screen anymore. The init-ordering fix (call process_shader before initialize on the first init path so init_chain doesn't read a calloc-zeroed fbo struct) is also included, since both regressions would have needed to be tested together anyway. Tested with snes9x2010 + DKC2 + sequence: stock -> testie-crt (7-pass, works) -> mdapt+xbr-hybrid+aa (mdapt-pass3 C5109 failure) -> stock (recovered). Pre-fix the third step stayed black; post- fix it goes back to plain stock rendering.
1 parent 72cb324 commit 64949e7

1 file changed

Lines changed: 88 additions & 7 deletions

File tree

gfx/drivers/d3d9cg.c

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,22 @@ static bool d3d9_cg_load_program(cg_renderchain_t *chain,
20452045
free(listing_f);
20462046
free(listing_v);
20472047

2048+
/* Drop any partially-created program objects so add_pass doesn't
2049+
* inherit half-loaded handles -- previously these leaked, and on
2050+
* cgD3D9LoadProgram failure pass->fprg was a valid Cg object
2051+
* (just not GPU-loaded) which the renderer would happily try to
2052+
* bind every frame. */
2053+
if (pass->fprg)
2054+
{
2055+
cgDestroyProgram((CGprogram)pass->fprg);
2056+
pass->fprg = NULL;
2057+
}
2058+
if (pass->vprg)
2059+
{
2060+
cgDestroyProgram((CGprogram)pass->vprg);
2061+
pass->vprg = NULL;
2062+
}
2063+
20482064
return false;
20492065
}
20502066

@@ -2697,17 +2713,27 @@ static bool d3d9_cg_renderchain_add_pass(void *data, const struct LinkInfo *info
26972713
cg_renderchain_t *chain = (cg_renderchain_t*)data;
26982714
d3d9_cg_renderchain_t *_chain = &chain->chain;
26992715

2716+
/* Zero everything first so the bail-out path has a well-defined
2717+
* struct to clean up: load_program may leave vprg/fprg as NULL
2718+
* on failure (stack garbage otherwise), and the post-fail cleanup
2719+
* in renderchain_free / set_shader fallback walks these fields. */
2720+
memset(&pass, 0, sizeof(pass));
27002721
pass.info = *info;
2701-
pass.last_width = 0;
2702-
pass.last_height = 0;
27032722
pass.attrib_map = (struct unsigned_vector_list*)
27042723
unsigned_vector_list_new();
27052724
pass.pool = D3DPOOL_DEFAULT;
27062725

2707-
d3d9_cg_load_program((cg_renderchain_t*)chain, &pass, info->pass->source.path, true);
2726+
/* Cg compile/link failure was previously swallowed here, leaving
2727+
* vprg/fprg NULL but the pass still appended to the chain. The
2728+
* render path then bound NULL programs every frame -> black
2729+
* screen with no recovery short of restarting the driver.
2730+
* Bail now and let init_chain unwind. */
2731+
if (!d3d9_cg_load_program((cg_renderchain_t*)chain, &pass,
2732+
info->pass->source.path, true))
2733+
goto error;
27082734

27092735
if (!d3d9_cg_renderchain_init_shader_fvf(_chain, &pass))
2710-
return false;
2736+
goto error;
27112737

27122738
if (!SUCCEEDED(IDirect3DDevice9_CreateVertexBuffer(
27132739
_chain->dev, 4 * sizeof(struct Vertex),
@@ -2717,7 +2743,7 @@ static bool d3d9_cg_renderchain_add_pass(void *data, const struct LinkInfo *info
27172743
vertbuf = NULL;
27182744

27192745
if (!vertbuf)
2720-
return false;
2746+
goto error;
27212747

27222748
pass.vertex_buf = vertbuf;
27232749

@@ -2735,7 +2761,7 @@ static bool d3d9_cg_renderchain_add_pass(void *data, const struct LinkInfo *info
27352761
}
27362762

27372763
if (!tex)
2738-
return false;
2764+
goto error;
27392765

27402766
pass.tex = tex;
27412767

@@ -2747,6 +2773,25 @@ static bool d3d9_cg_renderchain_add_pass(void *data, const struct LinkInfo *info
27472773
shader_pass_vector_list_append(_chain->passes, pass);
27482774

27492775
return true;
2776+
2777+
error:
2778+
/* Release whatever we managed to create before the failure point.
2779+
* pass is a stack local that's about to be discarded, so anything
2780+
* the chain doesn't take ownership of must be freed here. */
2781+
if (pass.vprg)
2782+
cgDestroyProgram((CGprogram)pass.vprg);
2783+
if (pass.fprg)
2784+
cgDestroyProgram((CGprogram)pass.fprg);
2785+
if (pass.vertex_decl)
2786+
IDirect3DVertexDeclaration9_Release(
2787+
(LPDIRECT3DVERTEXDECLARATION9)pass.vertex_decl);
2788+
if (vertbuf)
2789+
IDirect3DVertexBuffer9_Release(vertbuf);
2790+
if (tex)
2791+
IDirect3DTexture9_Release(tex);
2792+
if (pass.attrib_map)
2793+
free(pass.attrib_map);
2794+
return false;
27502795
}
27512796

27522797
static void d3d9_cg_renderchain_calc_and_set_shader_mvp(
@@ -3793,7 +3838,25 @@ static bool d3d9_cg_set_shader(void *data,
37933838

37943839
if (!d3d9_cg_process_shader(d3d) || !d3d9_cg_restore(d3d))
37953840
{
3796-
RARCH_ERR("[D3D9 Cg] Failed to set shader.\n");
3841+
/* The new preset failed to compile/link, and restore() left
3842+
* d3d->renderchain_data pointing at a half-built chain whose
3843+
* passes have NULL vprg/fprg. Rendering through that chain
3844+
* produces a black screen with no recovery path.
3845+
*
3846+
* Drop the bad preset and rebuild the chain against the stock
3847+
* shader so the user lands back in a working state. Returning
3848+
* false tells the caller their requested preset didn't take. */
3849+
RARCH_ERR("[D3D9 Cg] Failed to set shader, falling back to stock.\n");
3850+
3851+
if (d3d->shader_path)
3852+
{
3853+
free(d3d->shader_path);
3854+
d3d->shader_path = NULL;
3855+
}
3856+
3857+
if (!d3d9_cg_process_shader(d3d) || !d3d9_cg_restore(d3d))
3858+
RARCH_ERR("[D3D9 Cg] Stock-shader fallback also failed.\n");
3859+
37973860
return false;
37983861
}
37993862

@@ -3884,6 +3947,24 @@ static bool d3d9_cg_init_internal(d3d9_video_t *d3d,
38843947

38853948
d3d->video_info = *info;
38863949

3950+
/* Populate d3d->shader with stock-shader defaults (or parse a
3951+
* preset if d3d->shader_path is set) BEFORE the renderchain
3952+
* is built. d3d9_cg_initialize -> d3d9_cg_init_chain reads
3953+
* d3d->shader.pass[0].fbo to size the final pass; on the first
3954+
* init the d3d_video_t was just calloc'd, so without this call
3955+
* fbo.type_x/y are RARCH_SCALE_INPUT (=0) with scale=0.0f, and
3956+
* the renderchain ends up emitting a zero-sized quad — no core
3957+
* frame on screen until something later forces a chain rebuild
3958+
* via d3d9_cg_set_shader (which does call process_shader).
3959+
*
3960+
* d3d9hlsl gets away without this because its vertex positions
3961+
* are constant [0,1], so out_width/out_height being zero just
3962+
* means a uselessly-positioned-but-still-on-screen quad. The
3963+
* Cg renderchain uses pixel-space vertices (0..out_width,
3964+
* 0..out_height) and collapses to a point when those are zero. */
3965+
if (!d3d9_cg_process_shader(d3d))
3966+
return false;
3967+
38873968
if (!d3d9_cg_initialize(d3d, &d3d->video_info))
38883969
return false;
38893970

0 commit comments

Comments
 (0)