mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Declare a texture whose size query reads its layer count as an array, so the count is the texture's own.
A program that only asks a combined-handle texture for its size got that texture declared as plain 2D, the runtime bound a one-layer view, and the layer count read as 1 where the console answers the texture's real count. The game's particle vertex shaders read exactly that count, of a texture their pixel shaders sample as a 2D array, and use it as the frame count of a flipbook, so such particles stayed on their first frame. A size query that reads the component after a 1D or 2D type's dimensions now makes the declaration an array; a plain texture bound through an array view still reports one layer. 74 of the stored shader variants change (the contributor's sweep, all valid; a reader here found the same 74 in a shader cache on this PC), and the contributor's before-and-after pictures differ in the one scene tried, the Wallmaster's warning disc. Goes in with the image read capability so that installs translate their shaders again once. Built here; tools/xlate_smoke.py's new case reports the texture as plain 2D with the translator before the patch and as a 2D array with it, and all three modules validate. Not played with the new translator. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> (cherry picked from commit e4fac75190b4baf3811feef45b3302a9216cb8b2)
This commit is contained in:
@@ -99,8 +99,8 @@ def prepare_sources() -> Path:
|
||||
for junk in list(src.glob("src/*/obj")) + list(src.glob("src/*/bin")):
|
||||
shutil.rmtree(junk)
|
||||
patches = sorted((XLATE / "patches").glob("*.patch"))
|
||||
if len(patches) != 38:
|
||||
sys.exit(f"expected 38 patches, found {len(patches)}")
|
||||
if len(patches) != 39:
|
||||
sys.exit(f"expected 39 patches, found {len(patches)}")
|
||||
# build/xlate is inside this checkout, and git inside a repository reads a "diff --git" patch's paths from the
|
||||
# repository's top: those hunks fall outside the directory handed to --directory, git skips every file of the patch
|
||||
# and still exits 0. GIT_CEILING_DIRECTORIES stops git from walking up to the checkout, so it reads each patch as it
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
|
||||
@@ -1116,6 +1116,21 @@
|
||||
else
|
||||
{
|
||||
type = context.TranslatorContext.GpuAccessor.QuerySamplerType(imm);
|
||||
+
|
||||
+ // NX2NAVI: a size query that reads the component after a 1D or 2D type's dimensions asks for the layer
|
||||
+ // count, so the texture is an array. That holds for any 1D or 2D type here: the accessor's answer, or the 2D
|
||||
+ // it answers for a handle it has no type for. The game's particle vertex shaders read only the layer count
|
||||
+ // of such a handle, which their pixel shaders sample as a 2D array, and use it as the frame count of a
|
||||
+ // flipbook; declared 2D, the binding is a one-layer view and the count reads as 1. A plain texture bound
|
||||
+ // through an array view still reports one layer. Not handled: a program that samples the same handle as a
|
||||
+ // plain 2D texture before this query gets an arrayed declaration for that sample (invalid SPIR-V).
|
||||
+ if (query == TexQuery.TexHeaderDimension &&
|
||||
+ ((type & SamplerType.Mask) == SamplerType.Texture1D || (type & SamplerType.Mask) == SamplerType.Texture2D) &&
|
||||
+ !type.HasFlag(SamplerType.Array) &&
|
||||
+ (componentMask & (1 << type.Dimensions)) != 0)
|
||||
+ {
|
||||
+ type |= SamplerType.Array;
|
||||
+ }
|
||||
}
|
||||
|
||||
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
|
||||
@@ -1,10 +1,11 @@
|
||||
"""Smoke test for build/xlate/nx2navi_xlate(.exe): no game files needed.
|
||||
|
||||
Sends one hand-assembled Maxwell fragment shader (writes the constant colour 1.0, 0.5, 0.25, 1.0 to render target 0)
|
||||
through --serve, checks the reply, then sends a malformed line and checks the service survives and still answers. A
|
||||
hand-assembled compute shader that loads a texel from a float image and stores it back follows: the translator
|
||||
declares such an image without a format, and its read needs StorageImageReadWithoutFormat. Every module is validated
|
||||
with spirv-val.
|
||||
through --serve, checks the reply, then sends a malformed line and checks the service survives and still answers. Two
|
||||
hand-assembled compute shaders follow. One loads a texel from a float image and stores it back: the translator
|
||||
declares such an image without a format, and its read needs StorageImageReadWithoutFormat. The other reads only the
|
||||
layer count of texture handle 0x10, for which the translator has no type answer: the texture must be declared as an
|
||||
array, or the count reads as 1. Every module is validated with spirv-val.
|
||||
|
||||
python tools/xlate_smoke.py
|
||||
"""
|
||||
@@ -47,6 +48,7 @@ def fragment_code() -> bytes:
|
||||
|
||||
|
||||
SULD_P, SUST_P = 0x1D61, 0x1D65 # opcode bits 51-63 of SULD.P and SUST.P
|
||||
TXQ = 0x1BE9 # opcode bits 51-63 of TXQ (bound handle)
|
||||
|
||||
|
||||
def surface_p(opcode: int, data: int, coord: int, handle: int) -> int:
|
||||
@@ -61,6 +63,14 @@ def image_read_code() -> bytes:
|
||||
return struct.pack(f"<{len(words)}Q", *words)
|
||||
|
||||
|
||||
def layer_count_code() -> bytes:
|
||||
"""Compute code: R0 = the layer count (component 2 of the dimension query) of texture handle 0x10, stored to image 0."""
|
||||
txq = (TXQ << 51) | (0x10 << 36) | (0x4 << 31) | (1 << 22) | PT | (RZ << 8) | 0 # TexHeaderDimension, mask z, LOD RZ
|
||||
sust = (SUST_P << 51) | (IMAGE0 << 36) | (0x1 << 20) | PT | (RZ << 8) | 0 # SUST.P 1D R of R0 at x 0
|
||||
words = [SCHED, txq, sust, EXIT]
|
||||
return struct.pack(f"<{len(words)}Q", *words)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not EXE.is_file():
|
||||
print(f"missing {EXE}; run tools/build_xlate.py", file=sys.stderr)
|
||||
@@ -71,24 +81,32 @@ def main() -> int:
|
||||
code.write_bytes(fragment_code())
|
||||
image_code = tmp / "cs.bin"
|
||||
image_code.write_bytes(image_read_code())
|
||||
layer_code = tmp / "layers.bin"
|
||||
layer_code.write_bytes(layer_count_code())
|
||||
out = tmp / "out"
|
||||
job = {"key": "const_ps", "stage": "ps", "code": str(code), "debug": False, "fout": {"0": "float"}}
|
||||
image_job = {"key": "image_read_cs", "stage": "cs", "code": str(image_code), "debug": False, "local": [1, 1, 1]}
|
||||
layer_job = {"key": "layer_count_cs", "stage": "cs", "code": str(layer_code), "debug": False, "local": [1, 1, 1]}
|
||||
lines = [
|
||||
json.dumps({"job": job, "out": str(out)}),
|
||||
"this is not json",
|
||||
json.dumps({"job": dict(job, key="again"), "out": str(out)}),
|
||||
json.dumps({"job": image_job, "out": str(out)}),
|
||||
json.dumps({"job": layer_job, "out": str(out)}),
|
||||
]
|
||||
r = subprocess.run([str(EXE), "--serve"], input="\n".join(lines) + "\n", capture_output=True, text=True, timeout=60)
|
||||
replies = r.stdout.splitlines()
|
||||
print("replies:", replies)
|
||||
if (len(replies) != 4 or replies[0] != "ok const_ps" or not replies[1].startswith("fail ") or replies[2] != "ok again"
|
||||
or replies[3] != "ok image_read_cs"):
|
||||
if (len(replies) != 5 or replies[0] != "ok const_ps" or not replies[1].startswith("fail ") or replies[2] != "ok again"
|
||||
or replies[3] != "ok image_read_cs" or replies[4] != "ok layer_count_cs"):
|
||||
print("unexpected replies; stderr:", r.stderr, file=sys.stderr)
|
||||
return 1
|
||||
failed = 0
|
||||
for key in ("const_ps", "image_read_cs"):
|
||||
layer_types = [t["type"] for t in json.loads((out / "layer_count_cs.res.json").read_text())["textures"]]
|
||||
if layer_types != [0x103]: # SamplerType Texture2D (3) | Array (0x100)
|
||||
print(f"layer_count_cs: texture types {layer_types}, expected a 2D array [259]", file=sys.stderr)
|
||||
failed = 1
|
||||
for key in ("const_ps", "image_read_cs", "layer_count_cs"):
|
||||
spv = out / f"{key}.spv"
|
||||
data = spv.read_bytes()
|
||||
if len(data) < 20 or struct.unpack("<I", data[:4])[0] != 0x07230203:
|
||||
|
||||
Reference in New Issue
Block a user