Emulate N64 memory coverage (memcvg) - #2983
Open
gonetz wants to merge 1 commit into
Open
Conversation
The RDP keeps a 3 bit coverage value per pixel in the extra bits of 9 bit RDRAM. The blender reads it back as memory coverage and uses it to decide whether to blend at all, as the G_BL_A_MEM blend factor and as the source of the coverage written back to the framebuffer. GLideN64 calculated pixel coverage from barycentric coordinates but threw it away, so none of that could be emulated. Store the coverage in a screen sized R8UI image and read/write it in the fragment shader under fragment shader interlock, so that overlapping primitives see each other's coverage in draw order. The feature needs GL_ARB_fragment_shader_interlock, GL_NV_fragment_shader_interlock or GL_INTEL_fragment_shader_ordering plus image load/store; it is disabled gracefully when the driver has none of them. An image is used rather than a second color attachment because dual source blending, which the blender relies on, forbids multiple render targets. Emulated on top of that, following the hardware: - overflow/prewrap = (memcvg + cvg) & 8 and blend_en = force_blend || (!overflow && antialias_en && farther) - the CLR_ON_CVG branch of the blender, which selects the M input instead of the blend equation when coverage does not overflow - partial reject (dontblend), which passes the P input through for opaque pixels of a src alpha / inverse src alpha blender - the G_BL_A_MEM blend factor, fed with memcvg << 5 - all four cvg_dest modes: CLAMP, WRAP, ZAP and SAVE - fill rectangles, which write coverage 7 or 0 depending on the low bit of the fill color - rejected pixels leave the stored coverage untouched Coverage can be enebled along with N64 depth compare. Both need the same critical section, so they now share one interlock block placed at the top level of main() and ordered the way the hardware orders them: depth compare first, its "farther" result feeding blend_en, then the blender, then the coverage write back, then a single deferred discard. One deliberate deviation: on a full coverage pixel with blending off the hardware writes cvg-1, because the neighbouring primitive is guaranteed to write the same pixel again and to top the coverage up. GPU rasterization gives a pixel to one triangle only, so that second write never happens and the lowered value would leak into every later read of memory coverage along internal edges of a mesh, producing cracks. max(cvg-1, memcvg) reproduces the state the hardware reaches after both primitives are drawn. The cost is that a silhouette edge over an opaque surface keeps full coverage; nothing reads that value yet. See the comment in glsl_CombinerProgramBuilderCommon.cpp. Coverage scaling for ZMODE_INTERPENETRATING is not emulated. Also adds the debug.displayCoverage debug option, which shows the content of the coverage buffer on screen the way G_RM_VISCVG does on hardware. It works only when generalEmulation.enableCoverage is set.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The RDP keeps a 3 bit coverage value per pixel in the extra bits of 9 bit RDRAM. The blender reads it back as memory coverage and uses it to decide whether to blend at all, as the G_BL_A_MEM blend factor and as the source of the coverage written back to the framebuffer. GLideN64 calculated pixel coverage from barycentric coordinates but threw it away, so none of that could be emulated.
Store the coverage in a screen sized R8UI image and read/write it in the fragment shader under fragment shader interlock, so that overlapping primitives see each other's coverage in draw order. The feature needs GL_ARB_fragment_shader_interlock, GL_NV_fragment_shader_interlock or GL_INTEL_fragment_shader_ordering plus image load/store; it is disabled gracefully when the driver has none of them. An image is used rather than a second color attachment because dual source blending, which the blender relies on, forbids multiple render targets.
Emulated on top of that, following the hardware:
Coverage can be enebled along with N64 depth compare. Both need the same critical section, so they now share one interlock block placed at the top level of main() and ordered the way the hardware orders them: depth compare first, its "farther" result feeding blend_en, then the blender, then the coverage write back, then a single deferred discard.
One deliberate deviation: on a full coverage pixel with blending off the hardware writes cvg-1, because the neighbouring primitive is guaranteed to write the same pixel again and to top the coverage up. GPU rasterization gives a pixel to one triangle only, so that second write never happens and the lowered value would leak into every later read of memory coverage along internal edges of a mesh, producing cracks. max(cvg-1, memcvg) reproduces the state the hardware reaches after both primitives are drawn. The cost is that a silhouette edge over an opaque surface keeps full coverage; nothing reads that value yet. See the comment in glsl_CombinerProgramBuilderCommon.cpp.
Coverage scaling for ZMODE_INTERPENETRATING is not emulated.
Also adds the debug.displayCoverage debug option, which shows the content of the coverage buffer on screen the way G_RM_VISCVG does on hardware. It works only when generalEmulation.enableCoverage is set.