diff --git a/.gitignore b/.gitignore
index 1d72f61..577ceb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.parcel-cache
dist/
+coverage/
node_modules
.vscode
.idea
diff --git a/Docs.md b/Docs.md
index 30f7c78..deef264 100644
--- a/Docs.md
+++ b/Docs.md
@@ -855,5 +855,71 @@ Example:
asm_include("./some_ucode.inc");
```
+## Magma
+RSPL supports writing magma vertex shaders.
+To enable magma mode, pass `--magma` on the command line.
+When this mode is enabled, different syntax becomes available and the file must be structured slightly differently.
+
+### `shader`
+This is a special function that must be defined exactly once in your file.
+In a magma vertex shader, there are no commands, but instead a single entry point.
+This is the code that will be run whenever the shader is invoked.
+Example:
+```c++
+shader myshader()
+{
+ // ...
+}
+```
+
+### `uniform`
+Uniforms are a type of state which can be used to provide constant values as input to the shader.
+Multiple uniforms can be defined, and each of them is assigned a binding number.
+Example:
+```c++
+uniform<0> MATRICES
+{
+ vec16 MATRICES_MVP[4];
+ vec16 MATRICES_MV[4];
+}
+```
+
+### `attribute`
+This keyword is used to define the layout of input vertices.
+Multiple attributes can be defined, and each of them is assigned an input number.
+Attributes can be marked as optional.
+Example:
+```c++
+attribute<0> s16 POSITION[3];
+attribute<1> u32 COLOR?; // use '?' to mark the attribute as optional
+```
+
+### `@AttrLoader(string)`
+This annotation is used to mark an instruction as an attribute loader.
+Attribute loaders must be either scalar or vector load instructions, which is the case for any of the builtin `load()` functions.
+Magma will then automatically patch the offset of these instructions to the configured offset of the specified attribute.
+Therefore, the address register should point to the beginnning of a vertex, so that they load the value of the attribute.
+Pass the name of an attribute as the argument of the annotation.
+Example:
+```c++
+// No need to specify the offset, it will be automatically patched
+@AttrLoader("POSITION") position.xyzw = load(vertex_ptr).xyzw;
+```
+
+### `@AttrPatch(string)`
+This annotation is used to mark an instruction as an attribute patch.
+The string argument is the name of an attribute, plus an assembly instruction separated by a colon.
+When the patch is applied, the marked instruction is replaced by that assembly instruction.
+When an optional attribute is omitted, all patches of that attribute are applied.
+Example:
+```c++
+// When the COLOR attribute is omitted, this instruction gets replaced with vnop
+@AttrPatch("COLOR:vnop") vrgba:sfract *= vrgba_in:sfract;
+```
+
+### Further restrictions
+When magma mode is enabled, no commands may be defined.
+Also, any `state`, `data` or `bss` blocks may only contain `extern` labels.
+
## References
- RSP Instruction Set
diff --git a/src/cli.js b/src/cli.js
index bafb2d3..039c173 100644
--- a/src/cli.js
+++ b/src/cli.js
@@ -25,7 +25,8 @@ let config = {
defines: {},
patchFunctions: [],
watch: false,
- debugInfo: true
+ debugInfo: true,
+ magma: false
};
function getFunctionStartEnd(source, funcName) {
@@ -71,7 +72,16 @@ for(let i=3; i arg.includes(".mjs"));
//const worker = new WorkerThreads(config.optimizeWorker, selfPath);
diff --git a/src/lib/asmWriter.js b/src/lib/asmWriter.js
index 741f6c7..6f3c25d 100644
--- a/src/lib/asmWriter.js
+++ b/src/lib/asmWriter.js
@@ -9,12 +9,30 @@ import {ASM_TYPE} from "./intsructions/asmWriter.js";
import {REGS_SCALAR} from "./syntax/registers.js";
import {ANNOTATIONS, getAnnotationVal} from "./syntax/annotations.js";
+function getAttrLoaderLabel(asm) {
+ if (!asm.attrLoader) return null;
+ return "LOAD_" + asm.attrLoader + asm.debug.lineRSPL;
+}
+
+function getAttrPatchLabel(asm) {
+ if (!asm.attrPatch?.name) return null;
+ return "PATCH_" + asm.attrPatch.name + asm.debug.lineRSPL;
+}
+
+function getAsmLabels(asm) {
+ let labels = "";
+ for(const label of [getAttrLoaderLabel(asm), getAttrPatchLabel(asm)].filter(l => l)) {
+ labels += label + ": ";
+ }
+ return labels;
+}
+
/**
* @param {ASM} asm
* @returns {string}
*/
function stringifyInstr(asm) {
- return asm.op + (asm.args.length ? (" " + asm.args.join(", ")) : "");
+ return getAsmLabels(asm) + asm.op + (asm.args.length ? (" " + asm.args.join(", ")) : "");
}
/**
@@ -72,55 +90,7 @@ function writeStateVar(stateVar, writeLine) {
return byteSize;
}
-/**
- * Writes the ASM of all functions and the AST into a string.
- * @param {AST} ast
- * @param {ASMFunc[]} functionsAsm
- * @param {RSPLConfig} config
- * @returns {ASMOutput}
- */
-export function writeASM(ast, functionsAsm, config)
-{
- state.func = "(ASM)";
- state.line = 0;
-
- /** @type {ASMOutput} */
- const res = {
- asm: "",
- debug: {lineMap: {}, lineDepMap: {}, lineOptMap: {}, lineCycleMap: {}, lineStallMap: {}},
- sizeDMEM: 0,
- sizeIMEM: 0,
- };
-
- const writeLine = line => {
- res.asm += line + "\n";
- ++state.line;
- }
- const writeLines = lines => {
- if(lines.length === 0)return;
- res.asm += lines.join("\n") + "\n";
- state.line += lines.length;
- };
-
- writeLine("## Auto-generated file, transpiled with RSPL");
-
- if(ast.defines) {
- for(const [name, def] of Object.entries(ast.defines)) {
- writeLine(`#define ${name} ${def.value}`);
- }
- }
-
- const preIncs = generateIncs(ast.includes);
- for(const inc of preIncs)writeLine(inc);
-
- writeLines(["", ".set noreorder", ".set noat", ".set nomacro", ""]);
-
- REGS_SCALAR.forEach(reg => writeLine("#undef " + reg.substring(1)));
- REGS_SCALAR.forEach((reg, i) => writeLine(`.equ hex.${reg}, ${i}`));
- writeLine("#define vco 0");
- writeLine("#define vcc 1");
- writeLine("#define vce 2");
-
+function writeRSPQHeader(ast, functionsAsm, writeLine, writeLines) {
writeLines(["", ".data", " RSPQ_BeginOverlayHeader"]);
let commandList = [];
@@ -142,7 +112,6 @@ export function writeASM(ast, functionsAsm, config)
writeLines([" RSPQ_EndOverlayHeader", ""]);
let totalSaveByteSize = 0;
- let totalTextSize = 0;
const hasState = !!ast.state.find(v => !v.extern);
if(hasState) {
@@ -181,25 +150,167 @@ export function writeASM(ast, functionsAsm, config)
writeLines(["", ".text", "OVERLAY_CODE_START:", ""]);
+ return totalSaveByteSize;
+}
+
+/**
+ *
+ * @param {ASMFunc[]} functionsAsm
+ */
+function collectAttrLoaders(functionsAsm) {
+ const res = {};
+
+ const addEntry = (attrName) => {
+ if(!res[attrName]) {
+ res[attrName] = {
+ loaders: [],
+ patches: []
+ };
+ }
+ };
+
+ for(const asmFunc of functionsAsm) {
+ for(const asm of asmFunc.asm) {
+ if (asm.attrLoader) {
+ addEntry(asm.attrLoader);
+ res[asm.attrLoader].loaders.push(asm);
+ }
+ if (asm.attrPatch) {
+ addEntry(asm.attrPatch.name);
+ res[asm.attrPatch.name].patches.push(asm);
+ }
+ }
+ }
+ return res;
+}
+
+/**
+ *
+ * @param {AST} ast
+ * @param {ASMFunc[]} functionsAsm
+ * @param {(line: any) => void} writeLine
+ * @param {(line: any[]) => void} writeLines
+ * @returns
+ */
+function writeMagmaHeader(ast, functionsAsm, writeLine, writeLines) {
+ let totalSaveByteSize = 0;
+
+ writeLines(["", "MgBeginShaderUniforms"]);
+ for(const uniform of ast.uniforms) {
+ writeLine(` MgBeginUniform ${uniform.name}, ${uniform.binding}`);
+ for(const stateVar of uniform.state) {
+ if(stateVar.extern)continue;
+ totalSaveByteSize += writeStateVar(stateVar, writeLine);
+ }
+ writeLines([" MgEndUniform", ""]);
+ }
+ writeLines(["MgEndShaderUniforms", ""]);
+
+ const attrLoaders = collectAttrLoaders(functionsAsm);
+
+ writeLine("MgBeginVertexInput");
+ for(const attribute of ast.attributes) {
+ writeLine(` MgBeginVertexAttribute ${attribute.binding}, ${attribute.optional ? 1 : 0}`);
+ const loaders = attrLoaders[attribute.name]?.loaders;
+ if(typeof loaders !== 'undefined' && loaders.length > 0) {
+ writeLine(" MgVertexAttributeLoaders " + loaders.map(getAttrLoaderLabel).join(", "));
+ }
+ const patches = attrLoaders[attribute.name]?.patches;
+ if(patches) {
+ for(const patch of patches) {
+ writeLine(` MgBeginVertexAttributePatch ${getAttrPatchLabel(patch)}`);
+ writeLine(` ${patch.attrPatch.op || "nop"}`);
+ writeLine(" MgEndVertexAttributePatch");
+ };
+ }
+ writeLines([" MgEndVertexAttribute", ""]);
+ }
+ writeLines(["MgEndVertexInput", ""]);
+
+ writeLine("MgBeginShader");
+
+ return totalSaveByteSize;
+}
+
+/**
+ * Writes the ASM of all functions and the AST into a string.
+ * @param {AST} ast
+ * @param {ASMFunc[]} functionsAsm
+ * @param {RSPLConfig} config
+ * @returns {ASMOutput}
+ */
+export function writeASM(ast, functionsAsm, config)
+{
+ state.func = "(ASM)";
+ state.line = 0;
+
+ /** @type {ASMOutput} */
+ const res = {
+ asm: "",
+ debug: {lineMap: {}, lineDepMap: {}, lineOptMap: {}, lineCycleMap: {}, lineStallMap: {}},
+ sizeDMEM: 0,
+ sizeIMEM: 0,
+ };
+
+ const writeLine = line => {
+ res.asm += line + "\n";
+ ++state.line;
+ }
+ const writeLines = lines => {
+ if(lines.length === 0)return;
+ res.asm += lines.join("\n") + "\n";
+ state.line += lines.length;
+ };
+
+ writeLine("## Auto-generated file, transpiled with RSPL");
+
+ if(ast.defines) {
+ for(const [name, def] of Object.entries(ast.defines)) {
+ if (!def.value) {
+ writeLine(`#define ${name}`);
+ } else {
+ writeLine(`#define ${name} ${def.value}`);
+ }
+ }
+ }
+
+ const preIncs = generateIncs(ast.includes);
+ for(const inc of preIncs)writeLine(inc);
+
+ writeLines(["", ".set noreorder", ".set noat", ".set nomacro", ""]);
+
+ REGS_SCALAR.forEach(reg => writeLine("#undef " + reg.substring(1)));
+ REGS_SCALAR.forEach((reg, i) => writeLine(`.equ hex.${reg}, ${i}`));
+ writeLine("#define vco 0");
+ writeLine("#define vcc 1");
+ writeLine("#define vce 2");
+
+ let totalSaveByteSize = 0;
+ let totalTextSize = 0;
+
+ if(config.magma) {
+ totalSaveByteSize += writeMagmaHeader(ast, functionsAsm, writeLine, writeLines);
+ } else {
+ totalSaveByteSize += writeRSPQHeader(ast, functionsAsm, writeLine, writeLines);
+ }
+
if(!config.rspqWrapper) {
state.line = 1;
res.asm = "";
}
- for(const block of functionsAsm) {
- if(!["function", "command"].includes(block.type))continue;
- if(block.asm.length === 0)continue;
+ const writeFunction = block => {
+ if(block.asm.length === 0)return;
const align = getAnnotationVal(block.annotations, ANNOTATIONS.Align, 0) || 0;
- if(align)writeLine(`.align ${alignToExp(align)}`);
+ if (align) writeLine(`.align ${alignToExp(align)}`);
- writeLine(block.name + ":");
+ if (block.type !== "shader") writeLine(block.name + ":");
let lastAsm = block.asm[0] || null;
- for(const asm of block.asm)
- {
+ for (const asm of block.asm) {
// Debug Information
- if(!asm.debug.lineASM) {
+ if (!asm.debug.lineASM) {
asm.debug.lineASM = state.line;
} else {
asm.debug.lineASMOpt = state.line;
@@ -207,44 +318,43 @@ export function writeASM(ast, functionsAsm, config)
}
const lineRSPL = asm.debug.lineRSPL;
- if(!res.debug.lineMap[lineRSPL])res.debug.lineMap[lineRSPL] = [];
+ if (!res.debug.lineMap[lineRSPL]) res.debug.lineMap[lineRSPL] = [];
res.debug.lineMap[lineRSPL].push(asm.debug.lineASM);
- if(asm.debug.cycle) {
+ if (asm.debug.cycle) {
res.debug.lineCycleMap[asm.debug.lineASMOpt] = asm.debug.cycle;
res.debug.lineStallMap[asm.debug.lineASMOpt] = asm.debug.stall;
}
let debugInfo = '';
- if(config.debugInfo)
- {
- if(asm.debug.lineRSPL) {
+ if (config.debugInfo) {
+ if (asm.debug.lineRSPL) {
let cycleStr = ' ^';
let cycleDiff = asm.debug.cycle - lastAsm.debug.cycle;
- if(cycleDiff !== 0) {
+ if (cycleDiff !== 0) {
let stars = '';
- if(cycleDiff > 1) {
+ if (cycleDiff > 1) {
stars = '*'.repeat(cycleDiff - 1);
}
cycleStr = (stars + asm.debug.cycle.toString()).padStart(6, ' ');
}
- debugInfo += ` ## L:${asm.debug.lineRSPL.toString().padEnd(4, ' ')} | ${cycleStr} | ${state.sourceLines[asm.debug.lineRSPL-1] || ''}`;
+ debugInfo += ` ## L:${asm.debug.lineRSPL.toString().padEnd(4, ' ')} | ${cycleStr} | ${state.sourceLines[asm.debug.lineRSPL - 1] || ''}`;
}
- if(asm.funcArgs && asm.funcArgs.length) {
+ if (asm.funcArgs && asm.funcArgs.length) {
debugInfo += " ## Args: " + asm.funcArgs.join(", ");
}
- if(asm.barrierMask) {
+ if (asm.barrierMask) {
debugInfo += " ## Barrier: 0x" + asm.barrierMask.toString(16).toUpperCase();
}
}
let tag = '';
- for(const ann of asm.annotations) {
- if(ann.name === ANNOTATIONS.Tag) {
+ for (const ann of asm.annotations) {
+ if (ann.name === ANNOTATIONS.Tag) {
tag = `TAG_${ann.value}: `;
break;
}
@@ -253,31 +363,47 @@ export function writeASM(ast, functionsAsm, config)
// ASM Text output
switch (asm.type) {
case ASM_TYPE.INLINE:
- case ASM_TYPE.OP : writeLine(` ${tag}${stringifyInstr(asm).padEnd(debugInfo ? 50 : 0,' ')}${debugInfo}`);break;
- case ASM_TYPE.LABEL : writeLine(` ${tag}${asm.label}:`); break;
+ case ASM_TYPE.OP: writeLine(` ${tag}${stringifyInstr(asm).padEnd(debugInfo ? 50 : 0, ' ')}${debugInfo}`); break;
+ case ASM_TYPE.LABEL: writeLine(` ${tag}${asm.label}:`); break;
default: state.throwError("Unknown ASM type: " + asm.type, asm);
}
totalTextSize += asm.type === ASM_TYPE.OP ? 4 : 0;
- if(asm.type === ASM_TYPE.OP) {
+ if (asm.type === ASM_TYPE.OP) {
lastAsm = asm;
}
}
- for(const asm of block.asm)
- {
- if(!asm.debug.lineASMOpt)continue;
+ for (const asm of block.asm) {
+ if (!asm.debug.lineASMOpt) continue;
//console.log(asm.debug.lineASM, [asm.debug.reorderLineMin, asm.debug.reorderLineMax]);
res.debug.lineDepMap[asm.debug.lineASM] = [asm.debug.reorderLineMin, asm.debug.reorderLineMax];
}
}
+ if (config.magma) {
+ const shaderFunctions = functionsAsm.filter(fn => fn.type === "shader");
+ for (const block of shaderFunctions) {
+ writeFunction(block);
+ }
+ }
+
+ const regularFunctionsAsm = functionsAsm.filter(fn => ["function", "command"].includes(fn.type));
+ for (const block of regularFunctionsAsm) {
+ writeFunction(block);
+ }
+
writeLine("");
if(!config.rspqWrapper)return res;
- writeLine("OVERLAY_CODE_END:");
- writeLine("");
+ if(config.magma) {
+ writeLine("MgEndShader");
+ writeLine("");
+ } else {
+ writeLine("OVERLAY_CODE_END:");
+ writeLine("");
+ }
REGS_SCALAR.map((reg, i) => "#define " + reg.substring(1) + " $" + i)
.filter((_, i) => i !== 1)
diff --git a/src/lib/ast2asm.js b/src/lib/ast2asm.js
index 8b74029..08dc33a 100644
--- a/src/lib/ast2asm.js
+++ b/src/lib/ast2asm.js
@@ -400,6 +400,7 @@ function scopedBlockToASM(block, args = [], isCommand = false)
function getArgSize(block)
{
+ if(block.type === "shader")return 8;
if(block.type !== "command")return 0;
// each arg is always 4-bytes, the first one is implicitly set
return Math.max(block.args.length * 4, 4);
@@ -414,7 +415,9 @@ export function ast2asm(ast)
/** @type {ASMFunc[]} */
const res = [];
- for(const stateVar of [...ast.state, ...ast.stateData, ...ast.stateBss]) {
+ const uniformsState = ast.uniforms.flatMap(u => u.state);
+
+ for(const stateVar of [...ast.state, ...ast.stateData, ...ast.stateBss, ...uniformsState]) {
const arraySize = stateVar.arraySize.reduce((a, b) => a * b, 1) || 1;
state.declareMemVar(stateVar.varName, stateVar.varType, arraySize);
}
@@ -433,7 +436,7 @@ export function ast2asm(ast)
state.func = block.name || "";
state.line = block.line || 0;
- if(["function", "command"].includes(block.type)) {
+ if(["function", "command", "shader"].includes(block.type)) {
if(!block.body)continue;
state.enterFunction(block.name, block.type, getArgSize(block));
@@ -445,7 +448,7 @@ export function ast2asm(ast)
const needsReturn = !getAnnotationVal(block.annotations || [], ANNOTATIONS.NoReturn);
if(needsReturn) {
- if(block.type === "command") {
+ if(["command", "shader"].includes(block.type)) {
blockAsm.push(asm("j", [LABEL_CMD_LOOP]), asmNOP());
} else {
blockAsm.push(asm("jr", [REG.RA]), asmNOP());
diff --git a/src/lib/astNormalize.js b/src/lib/astNormalize.js
index ae8d310..8cec913 100644
--- a/src/lib/astNormalize.js
+++ b/src/lib/astNormalize.js
@@ -9,6 +9,29 @@ import {validateAnnotation} from "./syntax/annotations.js";
import builtins from "./builtins/functions.js";
import {astCalcNormalize} from "./astCalcNormalizer.js";
+/**
+ *
+ * @param {ASTStatement[]} statements
+ * @param {ASTStatement} currentStatement
+ * @returns {ASTStatement[]}
+ */
+function getPrecedingAnnotations(statements, currentStatement)
+{
+ const currentStatementIdx = statements.indexOf(currentStatement);
+ if (currentStatementIdx < 0)
+ return [];
+
+ /** @type {ASTStatement[]} */
+ let annotations = [];
+ // Search backwards from the current statement
+ for (let index = currentStatementIdx-1; index >= 0; index--) {
+ const st = statements[index];
+ if (st.type !== 'annotation') break;
+ annotations.splice(0, 0, st);
+ }
+ return annotations;
+}
+
/**
* @param {ASTScopedBlock} block
* @param {ASTState[]} astState
@@ -59,6 +82,8 @@ function normalizeScopedBlock(block, astState, macros)
case "varDeclAssign":
statements.push({...st, type: "varDecl", varName: st.varName.split(":")[0]});
if(st.calc) { // ... and ignore empty assignments
+ // Duplicate annotations to the assigment
+ statements.push(...getPrecedingAnnotations(block.statements, st));
statements.push({
type: "varAssignCalc",
varName: st.varName,
@@ -195,14 +220,16 @@ function normalizeScopedBlock(block, astState, macros)
/**
* @param {AST} ast
+ * @param {RSPLConfig} config
* @returns {ASTFunc[]}
*/
-export function astNormalizeFunctions(ast)
+export function astNormalizeFunctions(ast, config)
{
const astFunctions = ast.functions;
/** @type {ASTMacroMap} */
const macros = {};
+ let shaderCount = 0;
for(const block of astFunctions) {
if(["function", "command"].includes(block.type)) {
@@ -214,34 +241,132 @@ export function astNormalizeFunctions(ast)
}
for(const block of astFunctions) {
- if(!["function", "command", "macro"].includes(block.type) || !block.body)continue;
+ if(!["function", "command", "macro", "shader"].includes(block.type) || !block.body)continue;
for(const anno of block.annotations) {
validateAnnotation(anno);
}
- if(block.type === "command" && block.resultType === null) {
- state.throwError("Commands must specify an index (e.g. 'command<4>')!", block)
+ if(block.type === "command") {
+ if(config.magma) {
+ state.throwError("Commands must not be defined when compiling for magma (define a 'shader' instead)!", block);
+ }
+ if(block.resultType === null) {
+ state.throwError("Commands must specify an index (e.g. 'command<4>')!", block);
+ }
}
if(block.type === "macro") {
if(block.resultType != null) {
- state.throwError("Macros must not specify an result-type (use 'macro' without `< >`)!", block);
+ state.throwError("Macros must not specify a result-type (use 'macro' without `< >`)!", block);
}
if(builtins[block.name]) {
state.throwError(`Macro '${block.name}' shadows a builtin function! Please use another name.`);
}
-
macros[block.name] = block;
}
+
+ if(block.type === "shader") {
+ if(!config.magma) {
+ state.throwError("Shaders are only allowed when compiling for magma (pass '--magma' on the command line)!", block);
+ }
+ if(shaderCount > 0) {
+ state.throwError("A shader has already been defined!", block);
+ }
+ if(block.resultType != null) {
+ state.throwError("Shaders must not specify a result-type (use 'shader' without `< >`)!", block);
+ }
+ if(block.args.length > 0) {
+ state.throwError("Shaders must not specify arguments!", block);
+ }
+ shaderCount++;
+ }
+ }
+
+ if(config.magma && shaderCount === 0) {
+ state.throwError("Exactly one shader must be defined when compiling for magma (use 'shader')!");
}
for(const block of astFunctions) {
if(block.type !== "macro" && block.body) {
state.func = block.name || "";
- normalizeScopedBlock(block.body, [...ast.state, ...ast.stateData, ...ast.stateBss], macros);
+ const uniformsState = ast.uniforms.flatMap(u => u.state);
+ normalizeScopedBlock(block.body, [...ast.state, ...ast.stateData, ...ast.stateBss, ...uniformsState], macros);
}
}
return astFunctions;
-}
\ No newline at end of file
+}
+
+/**
+ * @param {AST} ast
+ * @param {RSPLConfig} config
+ * @returns {ASTState[]}
+ */
+export function astNormalizeState(ast, config)
+{
+ const astState = ast.state;
+ return astState;
+}
+
+/**
+ * @param {AST} ast
+ * @param {RSPLConfig} config
+ * @returns {ASTUniform[]}
+ */
+export function astNormalizeUniforms(ast, config)
+{
+ const astUniforms = ast.uniforms;
+
+ let curBindingNumber = 0;
+ const usedBindingNumbers = new Set(astUniforms.map(u => u.binding));
+
+ for(const uniform of astUniforms) {
+ if(!config.magma) {
+ state.throwError("Uniforms are only allowed when compiling for magma (pass '--magma' on the command line)!", uniform);
+ }
+ if(typeof uniform.binding === 'number') {
+ if(uniform.binding < 0 || uniform.binding >= 2**32) {
+ state.throwError("Uniform binding number must be in [0, 2^32)!", uniform);
+ }
+ curBindingNumber = uniform.binding;
+ } else {
+ while (usedBindingNumbers.has(curBindingNumber)) curBindingNumber++;
+ uniform.binding = curBindingNumber;
+ }
+ usedBindingNumbers.add(curBindingNumber);
+ }
+
+ return astUniforms;
+}
+
+/**
+ * @param {AST} ast
+ * @param {RSPLConfig} config
+ * @returns {ASTAttribute[]}
+ */
+export function astNormalizeAttributes(ast, config)
+{
+ const astAttributes = ast.attributes;
+
+ let curInputNumber = 0;
+ const usedInputNumbers = new Set(astAttributes.map(u => u.binding));
+
+ for(const attribute of astAttributes) {
+ if (!config.magma) {
+ state.throwError("Attributes are only allowed when compiling for magma (pass '--magma' on the command line)!", attribute);
+ }
+ if(typeof attribute.binding == 'number') {
+ if (attribute.binding < 0 || attribute.binding >= 2**32) {
+ state.throwError("Attribute input number must be in [0, 2^32)!", attribute);
+ }
+ curInputNumber = attribute.binding;
+ } else {
+ while (usedInputNumbers.has(curInputNumber)) curInputNumber++;
+ attribute.binding = curInputNumber;
+ }
+ usedInputNumbers.add(curInputNumber);
+ }
+
+ return astAttributes;
+}
diff --git a/src/lib/builtins/functions.js b/src/lib/builtins/functions.js
index e3f9b96..6519a7d 100644
--- a/src/lib/builtins/functions.js
+++ b/src/lib/builtins/functions.js
@@ -600,6 +600,7 @@ function max(varRes, args, swizzle) {
if(varArg0.type !== varArg1.type)state.throwError("Builtin max() requires both arguments to be of the same type!", args);
if(varArg0.type !== "vec16")state.throwError("Builtin max() requires both arguments to be of type vec16! (@TODO: add scalar)", args);
+ varArg1.swizzle = args[1].swizzle;
return opsVector.opCompare(varRes, varArg0, varArg1, ">=", undefined);
}
@@ -612,6 +613,7 @@ function min(varRes, args, swizzle) {
if(varArg0.type !== varArg1.type)state.throwError("Builtin min() requires both arguments to be of the same type!", args);
if(varArg0.type !== "vec16")state.throwError("Builtin min() requires both arguments to be of type vec16! (@TODO: add scalar)", args);
+ varArg1.swizzle = args[1].swizzle;
return opsVector.opCompare(varRes, varArg0, varArg1, "<", undefined);
}
diff --git a/src/lib/grammar.cjs b/src/lib/grammar.cjs
index 9ba5561..18ad018 100644
--- a/src/lib/grammar.cjs
+++ b/src/lib/grammar.cjs
@@ -32,7 +32,7 @@ const NORM_SWIZZLE = s => s.split("")
const moo = require("moo")
const lexer = moo.compile({
- String: /".*"/,
+ String: /".*?"/,
DataType: ["u8", "s8", "u16", "s16", "u32", "s32", "vec32", "vec16"],
Registers: [
@@ -65,7 +65,7 @@ const lexer = moo.compile({
".0", ".1", ".2", ".3", ".4", ".5", ".6", ".7",
], value: s => NORM_SWIZZLE(s.substring(1))},
- FunctionType: ["function", "command", "macro"],
+ FunctionType: ["function", "command", "macro", "shader"],
KWIf : "if",
KWLoop : "loop",
KWElse : "else",
@@ -79,6 +79,8 @@ const lexer = moo.compile({
KWUndef : "undef",
KWExit : "exit",
KWAlign : "alignas",
+ KWUniform : "uniform",
+ KWAttr : "attribute",
ValueHex: /0x[0-9A-Fa-f']+/,
ValueBin: /0b[0-1']+/,
@@ -139,16 +141,24 @@ var grammar = {
{"name": "main$ebnf$2$subexpression$1", "symbols": ["_", "SectionState"]},
{"name": "main$ebnf$2", "symbols": ["main$ebnf$2", "main$ebnf$2$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
{"name": "main$ebnf$3", "symbols": []},
- {"name": "main$ebnf$3$subexpression$1", "symbols": ["Function"]},
+ {"name": "main$ebnf$3$subexpression$1", "symbols": ["_", "Uniform"]},
{"name": "main$ebnf$3", "symbols": ["main$ebnf$3", "main$ebnf$3$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
{"name": "main$ebnf$4", "symbols": []},
- {"name": "main$ebnf$4$subexpression$1", "symbols": ["_", "SectionIncl"]},
+ {"name": "main$ebnf$4$subexpression$1", "symbols": ["_", "VertexAttribute"]},
{"name": "main$ebnf$4", "symbols": ["main$ebnf$4", "main$ebnf$4$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
- {"name": "main", "symbols": ["main$ebnf$1", "main$ebnf$2", "main$ebnf$3", "main$ebnf$4", "_"], "postprocess": d => ({
+ {"name": "main$ebnf$5", "symbols": []},
+ {"name": "main$ebnf$5$subexpression$1", "symbols": ["Function"]},
+ {"name": "main$ebnf$5", "symbols": ["main$ebnf$5", "main$ebnf$5$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
+ {"name": "main$ebnf$6", "symbols": []},
+ {"name": "main$ebnf$6$subexpression$1", "symbols": ["_", "SectionIncl"]},
+ {"name": "main$ebnf$6", "symbols": ["main$ebnf$6", "main$ebnf$6$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
+ {"name": "main", "symbols": ["main$ebnf$1", "main$ebnf$2", "main$ebnf$3", "main$ebnf$4", "main$ebnf$5", "main$ebnf$6", "_"], "postprocess": d => ({
includes: MAP_TAKE(d[0], 1),
states: MAP_TAKE(d[1], 1),
- functions: MAP_TAKE(d[2], 0),
- postIncludes: MAP_TAKE(d[3], 1),
+ uniforms: MAP_TAKE(d[2], 1),
+ attributes: MAP_TAKE(d[3], 1),
+ functions: MAP_TAKE(d[4], 0),
+ postIncludes: MAP_TAKE(d[5], 1),
}) },
{"name": "SectionIncl", "symbols": [(lexer.has("KWInclude") ? {type: "KWInclude"} : KWInclude), "_", (lexer.has("String") ? {type: "String"} : String)], "postprocess": d => d[2].value},
{"name": "SectionState$ebnf$1", "symbols": []},
@@ -157,6 +167,15 @@ var grammar = {
name: d[0].value,
vars: d[4]
}) },
+ {"name": "Uniform$ebnf$1", "symbols": ["RegNumDef"], "postprocess": id},
+ {"name": "Uniform$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
+ {"name": "Uniform$ebnf$2", "symbols": []},
+ {"name": "Uniform$ebnf$2", "symbols": ["Uniform$ebnf$2", "StateVarDef"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
+ {"name": "Uniform", "symbols": [(lexer.has("KWUniform") ? {type: "KWUniform"} : KWUniform), "Uniform$ebnf$1", "_", (lexer.has("VarName") ? {type: "VarName"} : VarName), "_", (lexer.has("BlockStart") ? {type: "BlockStart"} : BlockStart), "_", "Uniform$ebnf$2", (lexer.has("BlockEnd") ? {type: "BlockEnd"} : BlockEnd)], "postprocess": d => ({
+ name: d[3],
+ binding: d[1],
+ state: d[7]
+ })},
{"name": "StateVarDef$ebnf$1$subexpression$1", "symbols": [(lexer.has("KWExtern") ? {type: "KWExtern"} : KWExtern), "_"]},
{"name": "StateVarDef$ebnf$1", "symbols": ["StateVarDef$ebnf$1$subexpression$1"], "postprocess": id},
{"name": "StateVarDef$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
@@ -180,6 +199,19 @@ var grammar = {
{"name": "NumList", "symbols": ["ValueNumeric"], "postprocess": MAP_FIRST},
{"name": "NumList$subexpression$1", "symbols": ["NumList", "_", (lexer.has("Seperator") ? {type: "Seperator"} : Seperator), "_", "ValueNumeric"]},
{"name": "NumList", "symbols": ["NumList$subexpression$1"], "postprocess": d => MAP_FLATTEN_TREE(d[0], 0, 4)},
+ {"name": "VertexAttribute$ebnf$1", "symbols": ["RegNumDef"], "postprocess": id},
+ {"name": "VertexAttribute$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
+ {"name": "VertexAttribute$ebnf$2", "symbols": []},
+ {"name": "VertexAttribute$ebnf$2", "symbols": ["VertexAttribute$ebnf$2", "IndexDef"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
+ {"name": "VertexAttribute$ebnf$3", "symbols": [(lexer.has("QuestionMark") ? {type: "QuestionMark"} : QuestionMark)], "postprocess": id},
+ {"name": "VertexAttribute$ebnf$3", "symbols": [], "postprocess": function(d) {return null;}},
+ {"name": "VertexAttribute", "symbols": [(lexer.has("KWAttr") ? {type: "KWAttr"} : KWAttr), "VertexAttribute$ebnf$1", "_", (lexer.has("DataType") ? {type: "DataType"} : DataType), "_", (lexer.has("VarName") ? {type: "VarName"} : VarName), "VertexAttribute$ebnf$2", "VertexAttribute$ebnf$3", "_", (lexer.has("StmEnd") ? {type: "StmEnd"} : StmEnd)], "postprocess": d => ({
+ name: d[5],
+ binding: d[1],
+ type: d[3],
+ arraySize: d[6] || 1,
+ optional: !!d[7],
+ })},
{"name": "Function$ebnf$1", "symbols": []},
{"name": "Function$ebnf$1", "symbols": ["Function$ebnf$1", "Annotation"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
{"name": "Function$ebnf$2$subexpression$1", "symbols": ["RegDef"]},
diff --git a/src/lib/grammar.ne b/src/lib/grammar.ne
index be13f4a..a408d38 100644
--- a/src/lib/grammar.ne
+++ b/src/lib/grammar.ne
@@ -28,7 +28,7 @@ const NORM_SWIZZLE = s => s.split("")
const moo = require("moo")
const lexer = moo.compile({
- String: /".*"/,
+ String: /".*?"/,
DataType: ["u8", "s8", "u16", "s16", "u32", "s32", "vec32", "vec16"],
Registers: [
@@ -61,7 +61,7 @@ const lexer = moo.compile({
".0", ".1", ".2", ".3", ".4", ".5", ".6", ".7",
], value: s => NORM_SWIZZLE(s.substring(1))},
- FunctionType: ["function", "command", "macro"],
+ FunctionType: ["function", "command", "macro", "shader"],
KWIf : "if",
KWLoop : "loop",
KWElse : "else",
@@ -75,6 +75,8 @@ const lexer = moo.compile({
KWUndef : "undef",
KWExit : "exit",
KWAlign : "alignas",
+ KWUniform : "uniform",
+ KWAttr : "attribute",
ValueHex: /0x[0-9A-Fa-f']+/,
ValueBin: /0b[0-1']+/,
@@ -130,11 +132,13 @@ const lexer = moo.compile({
# Pass your lexer with @lexer:
@lexer lexer
-main -> (_ SectionIncl):* (_ SectionState):* (Function):* (_ SectionIncl):* _ {% d => ({
+main -> (_ SectionIncl):* (_ SectionState):* (_ Uniform):* (_ VertexAttribute):* (Function):* (_ SectionIncl):* _ {% d => ({
includes: MAP_TAKE(d[0], 1),
states: MAP_TAKE(d[1], 1),
- functions: MAP_TAKE(d[2], 0),
- postIncludes: MAP_TAKE(d[3], 1),
+ uniforms: MAP_TAKE(d[2], 1),
+ attributes: MAP_TAKE(d[3], 1),
+ functions: MAP_TAKE(d[4], 0),
+ postIncludes: MAP_TAKE(d[5], 1),
}) %}
######### Include-Section #########
@@ -146,6 +150,12 @@ SectionState -> %VarName _ %BlockStart _ StateVarDef:* %BlockEnd {% d => ({
vars: d[4]
}) %}
+Uniform -> %KWUniform RegNumDef:? _ %VarName _ %BlockStart _ StateVarDef:* %BlockEnd {% d => ({
+ name: d[3],
+ binding: d[1],
+ state: d[7]
+})%}
+
StateVarDef -> (%KWExtern _):? StateAlign:? %DataType _ %VarName IndexDef:* StateValueDef:? _ %StmEnd _ {% d => ({
type: "varState",
extern: !!d[0],
@@ -163,6 +173,13 @@ StateAlign -> %KWAlign %ArgsStart ValueNumeric %ArgsEnd _ {% d => d[2][0] %}
NumList -> ValueNumeric {% MAP_FIRST %}
| (NumList _ %Seperator _ ValueNumeric) {% d => MAP_FLATTEN_TREE(d[0], 0, 4) %}
+VertexAttribute -> %KWAttr RegNumDef:? _ %DataType _ %VarName IndexDef:* %QuestionMark:? _ %StmEnd {% d => ({
+ name: d[5],
+ binding: d[1],
+ type: d[3],
+ arraySize: d[6] || 1,
+ optional: !!d[7],
+})%}
######### Function-Section #########
diff --git a/src/lib/intsructions/asmWriter.js b/src/lib/intsructions/asmWriter.js
index 914e43c..61c3944 100644
--- a/src/lib/intsructions/asmWriter.js
+++ b/src/lib/intsructions/asmWriter.js
@@ -52,6 +52,18 @@ function getDebugData() {
};
}
+/**
+ *
+ * @returns {ASMAttrPatch}
+ */
+function getAttrPatch(annotations) {
+ const annoVal = getAnnotationVal(annotations, "AttrPatch");
+ if (!annoVal) return undefined;
+
+ const [name, op, ...rest] = annoVal.split(":");
+ return { name, op };
+}
+
/**
*
* @param op
@@ -75,6 +87,8 @@ function getOpInfo(op, type = ASM_TYPE.OP) {
annotations,
depsArgMask: 0n,
type: type,
+ attrLoader: getAnnotationVal(annotations, "AttrLoader"),
+ attrPatch: getAttrPatch(annotations),
};
if((res.opFlags & OP_FLAG_IS_BRANCH) && (res.opFlags & OP_FLAG_IS_LIKELY)) {
res.opFlags |= OP_FLAG_LIKELY_BRANCH;
diff --git a/src/lib/memory/memoryValidator.js b/src/lib/memory/memoryValidator.js
index 30eea8f..0bbe195 100644
--- a/src/lib/memory/memoryValidator.js
+++ b/src/lib/memory/memoryValidator.js
@@ -12,8 +12,9 @@ const STATE_NAMES = ["state", "data", "bss"];
* validates state memory
* @param {AST} ast
* @param {Array<{name: string; vars: ASTState[]}>} states
+ * @param {RSPLConfig} config
*/
-export const validateMemory = (ast, states) =>
+export const validateMemory = (ast, states, config) =>
{
state.func = "state";
@@ -52,6 +53,10 @@ export const validateMemory = (ast, states) =>
let currentAddr = 0;
let lastVar = stateVars[0];
for(const stateVar of stateVars) {
+ if (config.magma && !stateVar.extern) {
+ state.throwError("Only extern states are allowed when compiling for magma!", stateVar);
+ }
+
const arraySize = stateVar.arraySize.reduce((a, b) => a * b, 1) || 1;
const byteSize = TYPE_SIZE[stateVar.varType] * arraySize;
let align = stateVar.align || (2 ** TYPE_ALIGNMENT[stateVar.varType]);
diff --git a/src/lib/operations/vector.js b/src/lib/operations/vector.js
index 7cea768..e96d7a8 100644
--- a/src/lib/operations/vector.js
+++ b/src/lib/operations/vector.js
@@ -613,7 +613,7 @@ function opMul(varRes, varLeft, varRight, clearAccum)
&& varLeft.type === "vec32" && varRight.type === "vec32"
) {
return [
- asm("vmudl", [REG.VTEMP0, fractReg(varLeft), fractReg(varRight) + swizzleRight]),
+ asm(fractOp, [REG.VTEMP0, fractReg(varLeft), fractReg(varRight) + swizzleRight]),
asm("vmadm", [REG.VTEMP0, intReg(varLeft), fractReg(varRight) + swizzleRight]),
asm("vmadn", [varRes.reg, fractReg(varLeft), intReg(varRight) + swizzleRight]),
];
@@ -640,15 +640,28 @@ function opMul(varRes, varLeft, varRight, clearAccum)
}
}
+ const leftIsFraction = ["sfract", "ufract"].includes(varLeft.castType);
+ const resIsFraction = ["sfract", "ufract"].includes(varRes.castType);
+
// 16bit * 32bit multiply
- if(right32Bit && varRes.type === "vec32" && varLeft.type === "vec16" && !["sfract", "ufract"].includes(varLeft.castType)) {
+ if(right32Bit && varRes.type === "vec32" && varLeft.type === "vec16" && !leftIsFraction) {
+ const fractOp = clearAccum ? "vmudm" : "vmadm";
return [
- asm("vmudm", [resRegs[1], varLeft.reg, fractReg(varRight) + swizzleRight]),
+ asm(fractOp, [resRegs[1], varLeft.reg, fractReg(varRight) + swizzleRight]),
asm("vmadh", [resRegs[0], varLeft.reg, intReg(varRight) + swizzleRight]),
asm("vmadn", [resRegs[1], REGS.VZERO, REGS.VZERO]),
];
}
+ // 16bit * 32bit multiply with 16bit result
+ if(right32Bit && varRes.type === "vec16" && varLeft.type === "vec16" && !leftIsFraction && !resIsFraction) {
+ const fractOp = clearAccum ? "vmudm" : "vmadm";
+ return [
+ asm(fractOp, [REG.VTEMP0, varLeft.reg, fractReg(varRight) + swizzleRight]),
+ asm("vmadh", [resRegs[0], varLeft.reg, intReg(varRight) + swizzleRight]),
+ ];
+ }
+
const rightSideIsFraction = ["sfract", "ufract"].includes(varRight.castType);
// Full 32-bit multiplication
@@ -661,15 +674,34 @@ function opMul(varRes, varLeft, varRight, clearAccum)
} // Partial multiplication: s16.16 * 0.16 (fractional part of original s16.16)
else if(rightSideIsFraction && (varRight.originalType === "vec32" || varRes.type === "vec32"))
{
+ if(varLeft.type === "vec32") {
+ res.push(
+ asm(fractOp, [nextVecReg(varRes.reg), fractReg(varLeft), varRight.reg + swizzleRight]),
+ asm("vmadm", [varRes.reg, varLeft.reg, varRight.reg + swizzleRight]),
+ );
+ } else {
+ const fractOp = clearAccum ? "vmudm" : "vmadm";
+ res.push(
+ asm(fractOp, [varRes.reg, varLeft.reg, varRight.reg + swizzleRight]),
+ );
+ }
res.push(
- asm(fractOp, [nextVecReg(varRes.reg), fractReg(varLeft), varRight.reg + swizzleRight]),
- asm("vmadm", [ varRes.reg, varLeft.reg, varRight.reg + swizzleRight]),
- asm("vmadn", [nextVecReg(varRes.reg), REGS.VZERO, REGS.VZERO]),
+ asm("vmadn", [nextVecReg(varRes.reg), REGS.VZERO, REGS.VZERO]),
);
return res;
} // 16-Bit multiplication
- else if(varRes.type === "vec16")
+ else if(varRes.type === "vec16" && varLeft.type == "vec16")
{
+ if(varLeft.castType === "ufract" && varRight.castType === "sint") {
+ intOp = clearAccum ? "vmudn": "vmadn";
+ return [asm(intOp, [varRes.reg, varLeft.reg, varRight.reg + swizzleRight])];
+ }
+
+ if(varLeft.castType === "sint" && varRight.castType === "ufract") {
+ intOp = clearAccum ? "vmudm": "vmadm";
+ return [asm(intOp, [varRes.reg, varLeft.reg, varRight.reg + swizzleRight])];
+ }
+
const caseRef = varLeft.castType || varRight.castType || varRes.castType;
if(caseRef === "ufract" || caseRef === "sfract")
{
diff --git a/src/lib/preproc/preprocess.js b/src/lib/preproc/preprocess.js
index ba1f082..0ba6048 100644
--- a/src/lib/preproc/preprocess.js
+++ b/src/lib/preproc/preprocess.js
@@ -40,17 +40,17 @@ export function preprocess(src, defines = {}, fileLoader = undefined)
if (!ignoreLine && lineTrimmed.startsWith("#define"))
{
- const parts = lineTrimmed.match(/#define\s+([a-zA-Z0-9_]+)\s+(.*)/);
+ const parts = lineTrimmed.match(/#define\s+([a-zA-Z0-9_]+)(\s+.*)?/);
if(!parts)throw new Error(`Line ${i+1}: Invalid #define statement!`);
let [_, name, value] = parts;
for (const data of Object.values(defines)) {
- value = value.replace(data.regex, data.value);
+ value = value?.replace(data.regex, data.value);
}
defines[name] = {
regex: new RegExp(`(\\b${name}\\b)|(\\$\\{${name}\\})`, "g"),
- value
+ value: value?.trim() ?? ""
};
}
else if (!ignoreLine && lineTrimmed.startsWith("#undef"))
diff --git a/src/lib/syntax/annotations.js b/src/lib/syntax/annotations.js
index f6e3c5d..26e810a 100644
--- a/src/lib/syntax/annotations.js
+++ b/src/lib/syntax/annotations.js
@@ -11,7 +11,9 @@ export const ANNOTATIONS = {
NoReturn: "NoReturn",
Unlikely: "Unlikely",
NoRegAlloc: "NoRegAlloc",
- Tag: "Tag"
+ Tag: "Tag",
+ AttrLoader: "AttrLoader",
+ AttrPatch: "AttrPatch"
};
export const KNOWN_ANNOTATIONS = Object.keys(ANNOTATIONS);
@@ -27,14 +29,17 @@ export function validateAnnotation(anno) {
}
// string annotations
- if(["Barrier"].includes(anno.name)) {
+ if(["Barrier", "AttrLoader", "AttrPatch"].includes(anno.name)) {
if(typeof anno.value !== "string") {
state.throwError("Annotation '"+anno.name+"' expects a string value!");
}
+ if(anno.value === "") {
+ state.throwError("Annotation '"+anno.name+"' expects a non-empty string value!");
+ }
}
}
export function getAnnotationVal(annotations, name) {
const anno = annotations.find(anno => anno.name === name);
return anno ? (anno.value || true) : undefined;
-}
\ No newline at end of file
+}
diff --git a/src/lib/transpiler.js b/src/lib/transpiler.js
index ae0e2eb..d070dbc 100644
--- a/src/lib/transpiler.js
+++ b/src/lib/transpiler.js
@@ -5,7 +5,7 @@
import { ast2asm } from "./ast2asm";
import { writeASM } from "./asmWriter";
-import {astNormalizeFunctions} from "./astNormalize";
+import {astNormalizeFunctions, astNormalizeState, astNormalizeUniforms, astNormalizeAttributes} from "./astNormalize";
import nearly from "nearley";
import grammarDef from "./grammar.cjs";
import state from "./state.js";
@@ -94,9 +94,12 @@ export async function transpile(ast, updateCb, config = {})
state.reset();
normalizeConfig(config);
- validateMemory(ast, ast.states);
+ ast.state = astNormalizeState(ast, config);
+ ast.uniforms = astNormalizeUniforms(ast, config);
+ ast.attributes = astNormalizeAttributes(ast, config);
+ validateMemory(ast, ast.states, config);
- ast.functions = astNormalizeFunctions(ast);
+ ast.functions = astNormalizeFunctions(ast, config);
const functionsAsm = ast2asm(ast);
for(const func of functionsAsm) {
diff --git a/src/lib/types/asm.d.ts b/src/lib/types/asm.d.ts
index 5852152..dd279f7 100644
--- a/src/lib/types/asm.d.ts
+++ b/src/lib/types/asm.d.ts
@@ -20,6 +20,11 @@ declare global
paired: boolean;
};
+ type ASMAttrPatch = {
+ name: string;
+ op: string;
+ }
+
type ASM = {
type: ASMType;
op: string;
@@ -53,6 +58,9 @@ declare global
debug: ASMDebug;
annotations: Annotation[];
+
+ attrLoader?: string;
+ attrPatch?: ASMAttrPatch;
};
type ASMFunc = ASTFunc | {
diff --git a/src/lib/types/ast.d.ts b/src/lib/types/ast.d.ts
index 9c2ef18..265c7c2 100644
--- a/src/lib/types/ast.d.ts
+++ b/src/lib/types/ast.d.ts
@@ -44,6 +44,20 @@ declare global
value: number[] | undefined;
};
+ type ASTUniform = {
+ name: string;
+ binding?: number;
+ state: ASTState[];
+ };
+
+ type ASTAttribute = {
+ name: string;
+ binding?: number;
+ type: string;
+ arraySize: number[];
+ optional: boolean;
+ };
+
type ASTStatementBase = { line: number; };
type ASTScopedBlock = ASTStatementBase & {
@@ -167,6 +181,8 @@ declare global
state: ASTState[];
stateData: ASTState[];
stateBss: ASTState[];
+ uniforms: ASTUniform[];
+ attributes: ASTAttribute[];
functions: ASTFunc[];
postIncludes: string[];
defines: Record;
diff --git a/src/lib/types/types.d.ts b/src/lib/types/types.d.ts
index 9895644..217d44c 100644
--- a/src/lib/types/types.d.ts
+++ b/src/lib/types/types.d.ts
@@ -58,6 +58,7 @@ declare global {
optimizeWorker: number;
reorder: boolean;
rspqWrapper: boolean;
+ magma: boolean;
fileLoader: (path: string) => string;
defines: Record;
patchFunctions: string[];
diff --git a/src/tests/annotations.test.js b/src/tests/annotations.test.js
index e51891c..28267e0 100644
--- a/src/tests/annotations.test.js
+++ b/src/tests/annotations.test.js
@@ -76,4 +76,31 @@ caller:
jr $ra
nop`);
});
+
+ test('Tag', async () => {
+ const {asm, warn} = await transpileSource(`function test_tag() {
+ u32<$t0> a;
+ @Tag("TEST") a = 1;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(
+`test_tag:
+ TAG_TEST: addiu $t0, $zero, 1
+ jr $ra
+ nop`);
+ });
+
+ test('Tag (declaration-assignment)', async () => {
+ const {asm, warn} = await transpileSource(`function test_tag() {
+ @Tag("TEST") u32<$t0> a = 1;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(
+`test_tag:
+ TAG_TEST: addiu $t0, $zero, 1
+ jr $ra
+ nop`);
+ });
});
\ No newline at end of file
diff --git a/src/tests/examples.test.js b/src/tests/examples.test.js
index ae5771d..f0070c1 100644
--- a/src/tests/examples.test.js
+++ b/src/tests/examples.test.js
@@ -62,6 +62,15 @@ describe('Examples', () =>
expect(asm).toEqual(expectedASM);
});
+ test('Example code - Mgfx', async () => {
+ const code = readFileSync("./src/tests/examples/rsp_mgfx.rspl", "utf8");
+ const expectedASM = readFileSync("./src/tests/examples/rsp_mgfx.S", "utf8");
+
+ const {asm, warn} = await transpileSource(code, {rspqWrapper: true, optimize: true, debugInfo: true, magma: true});
+ expect(warn.toLowerCase()).not.toContain("error");
+ expect(asm).toEqual(expectedASM);
+ });
+
test('Example code - Mandelbrot', async () => {
const code = readFileSync("./src/tests/examples/mandelbrot.rspl", "utf8");
const {asm, warn} = await transpileSource(code, CONF);
diff --git a/src/tests/examples/rsp_mgfx.S b/src/tests/examples/rsp_mgfx.S
new file mode 100644
index 0000000..25a5032
--- /dev/null
+++ b/src/tests/examples/rsp_mgfx.S
@@ -0,0 +1,487 @@
+## Auto-generated file, transpiled with RSPL
+#define MG_VTX_XYZ 0
+#define MG_VTX_CLIP_CODE 6
+#define MG_VTX_TR_CODE 7
+#define MG_VTX_RGBA 8
+#define MG_VTX_ST 12
+#define MG_VTX_Wi 16
+#define MG_VTX_Wf 18
+#define MG_VTX_INVWi 20
+#define MG_VTX_INVWf 22
+#define MG_VTX_CS_POSi 24
+#define MG_VTX_CS_POSf 32
+#define MG_VTX_SIZE 40
+#define MG_VTX_SIZE2 80
+#define MGFX_ATTRIBUTE_POSITION 0
+#define MGFX_ATTRIBUTE_NORMAL 1
+#define MGFX_ATTRIBUTE_COLOR 2
+#define MGFX_ATTRIBUTE_TEXCOORD 3
+#define MGFX_BINDING_MATRICES 0
+#define MGFX_BINDING_TEXTURING 1
+#define MGFX_BINDING_LIGHTING 2
+#define MGFX_BINDING_FOG 3
+#define MGFX_LIGHT_COUNT_MAX 8
+#define MGFX_LIGHT_POSITION 0
+#define MGFX_LIGHT_POSITIONW 6
+#define MGFX_LIGHT_COLOR 8
+#define MGFX_LIGHT_INTENSITY 14
+#define MGFX_LIGHT_SIZE 16
+#define MGFX_MATRIX_SIZE 64
+#define MGFX_VTX_TEX_SHIFT 5
+#define SCRATCH_TEXSCALE 0x80
+#include
+
+.set noreorder
+.set noat
+.set nomacro
+
+#undef zero
+#undef at
+#undef v0
+#undef v1
+#undef a0
+#undef a1
+#undef a2
+#undef a3
+#undef t0
+#undef t1
+#undef t2
+#undef t3
+#undef t4
+#undef t5
+#undef t6
+#undef t7
+#undef s0
+#undef s1
+#undef s2
+#undef s3
+#undef s4
+#undef s5
+#undef s6
+#undef s7
+#undef t8
+#undef t9
+#undef k0
+#undef k1
+#undef gp
+#undef sp
+#undef fp
+#undef ra
+.equ hex.$zero, 0
+.equ hex.$at, 1
+.equ hex.$v0, 2
+.equ hex.$v1, 3
+.equ hex.$a0, 4
+.equ hex.$a1, 5
+.equ hex.$a2, 6
+.equ hex.$a3, 7
+.equ hex.$t0, 8
+.equ hex.$t1, 9
+.equ hex.$t2, 10
+.equ hex.$t3, 11
+.equ hex.$t4, 12
+.equ hex.$t5, 13
+.equ hex.$t6, 14
+.equ hex.$t7, 15
+.equ hex.$s0, 16
+.equ hex.$s1, 17
+.equ hex.$s2, 18
+.equ hex.$s3, 19
+.equ hex.$s4, 20
+.equ hex.$s5, 21
+.equ hex.$s6, 22
+.equ hex.$s7, 23
+.equ hex.$t8, 24
+.equ hex.$t9, 25
+.equ hex.$k0, 26
+.equ hex.$k1, 27
+.equ hex.$gp, 28
+.equ hex.$sp, 29
+.equ hex.$fp, 30
+.equ hex.$ra, 31
+#define vco 0
+#define vcc 1
+#define vce 2
+
+MgBeginShaderUniforms
+ MgBeginUniform MATRICES, 0
+ .align 4
+ MATRICES_MVP: .ds.b 64
+ .align 4
+ MATRICES_MV: .ds.b 64
+ .align 4
+ MATRICES_NORMAL: .ds.b 32
+ MgEndUniform
+
+ MgBeginUniform TEXTURING, 1
+ .align 1
+ TEXTURING_SCALE: .ds.b 4
+ .align 1
+ TEXTURING_OFFSET: .ds.b 4
+ MgEndUniform
+
+ MgBeginUniform LIGHTING, 2
+ LIGHTING_LIGHTS: .ds.b 128
+ .align 1
+ LIGHTING_AMBIENT: .ds.b 6
+ LIGHTING_HAS_POINTS: .ds.b 1
+ LIGHTING_COUNT: .ds.b 1
+ MgEndUniform
+
+ MgBeginUniform FOG, 3
+ .align 1
+ FOG_PARAMS: .ds.b 6
+ FOG_MASK: .ds.b 1
+ MgEndUniform
+
+MgEndShaderUniforms
+
+MgBeginVertexInput
+ MgBeginVertexAttribute 0, 0
+ MgVertexAttributeLoaders LOAD_POSITION243, LOAD_POSITION244, LOAD_POSITION505, LOAD_POSITION506
+ MgEndVertexAttribute
+
+ MgBeginVertexAttribute 1, 1
+ MgVertexAttributeLoaders LOAD_NORMAL241, LOAD_NORMAL242, LOAD_NORMAL503, LOAD_NORMAL504
+ MgEndVertexAttribute
+
+ MgBeginVertexAttribute 2, 1
+ MgVertexAttributeLoaders LOAD_COLOR245, LOAD_COLOR246, LOAD_COLOR507, LOAD_COLOR508
+ MgBeginVertexAttributePatch PATCH_COLOR432
+ vnop
+ MgEndVertexAttributePatch
+ MgEndVertexAttribute
+
+ MgBeginVertexAttribute 3, 1
+ MgVertexAttributeLoaders LOAD_TEXCOORD270, LOAD_TEXCOORD273
+ MgEndVertexAttribute
+
+MgEndVertexInput
+
+MgBeginShader
+ addiu $t0, $gp, %lo(RSPQ_DMEM_BUFFER) -6 ## L:146 | ^ | u32 inputs_ptr = get_cmd_address(0x2);
+ ori $at, $zero, %lo(MG_VERTEX_SIZE) ## L:147 | 2 | vec16 vvtx_size = load(MG_VERTEX_SIZE).xyzw;
+ ldv $v01, 0, 0, $at ## L:147 | 3 | vec16 vvtx_size = load(MG_VERTEX_SIZE).xyzw;
+ ldv $v02, 0, 0, $t0 ## L:148 | 4 | vec16 vinputs = load(inputs_ptr).xyzw;
+ vmudh $v04, $v02, $v01.v ## L:150 | ***8 | vec32 vproducts = vinputs * vvtx_size;
+ vsar $v03, COP2_ACC_HI ## L:150 | 9 | vec32 vproducts = vinputs * vvtx_size;
+ vsar $v04, COP2_ACC_MD ## L:150 | 10 | vec32 vproducts = vinputs * vvtx_size;
+ ori $at, $zero, %lo(RSPQ_SCRATCH_MEM) ## L:151 | ^ | @Barrier("inputs0") store(vproducts:ufract.xyzw, RSPQ_SCRATCH_MEM); ## Barrier: 0x1
+ sdv $v04, 0, 0, $at ## L:151 | ***14 | @Barrier("inputs0") store(vproducts:ufract.xyzw, RSPQ_SCRATCH_MEM); ## Barrier: 0x1
+ ssv $v03, 0, 8, $at ## L:152 | 15 | @Barrier("inputs1") store(vproducts.x, RSPQ_SCRATCH_MEM, 0x8); ## Barrier: 0x2
+ ssv $v04, 0, 10, $at ## L:152 | 16 | @Barrier("inputs1") store(vproducts.x, RSPQ_SCRATCH_MEM, 0x8); ## Barrier: 0x2
+ lhu $s7, %lo(RSPQ_SCRATCH_MEM + 2) ## L:158 | 17 | @Barrier("inputs0") vtx_out0_ptr = load(RSPQ_SCRATCH_MEM, 0x2); ## Barrier: 0x1
+ lhu $t1, %lo(RSPQ_SCRATCH_MEM + 4) ## L:160 | 18 | @Barrier("inputs0") vertices_size = load(RSPQ_SCRATCH_MEM, 0x4); ## Barrier: 0x1
+ lw $t2, %lo(RSPQ_SCRATCH_MEM + 8) ## L:162 | 19 | @Barrier("inputs1") buffer_offset = load(RSPQ_SCRATCH_MEM, 0x8); ## Barrier: 0x2
+ lw $s0, %lo(MG_VERTEX_BUFFER + 0) ## L:164 | 20 | u32<$s0> vtx_buffer = load(MG_VERTEX_BUFFER);
+ addu $s0, $s0, $t2 ## L:165 | **23 | vtx_buffer += buffer_offset;
+ andi $t3, $t2, 7 ## L:167 | 24 | u32 misalignment = buffer_offset & 0x7;
+ addu $t0, $t1, $t3 ## L:168 | 25 | dma_size = vertices_size + misalignment;
+ addiu $t0, $t0, 7 ## L:169 | 26 | dma_size += 0x7;
+ andi $t0, $t0, 4088 ## L:170 | 27 | dma_size &= 0xFF8;
+ ori $s4, $zero, %lo(MG_VERTEX_CACHE_END) ## L:172 | 28 | vtx_in0_ptr = MG_VERTEX_CACHE_END;
+ subu $s4, $s4, $t0 ## L:173 | 29 | vtx_in0_ptr -= dma_size;
+ addiu $t2, $zero, 12 ## L:175 | 30 | dma_in(vtx_in0_ptr, vtx_buffer, dma_size);
+ jal DMAExec ## L:175 | 31 | dma_in(vtx_in0_ptr, vtx_buffer, dma_size); ## Args: $t0, $t1, $s0, $s4, $t2
+ addiu $t0, $t0, -1 ## L:175 | *33 | dma_in(vtx_in0_ptr, vtx_buffer, dma_size);
+ lhu $t8, %lo(MG_VERTEX_SIZE + 0) ## L:177 | 34 | vtx_size = load(MG_VERTEX_SIZE);
+ sll $t9, $t8, 1 ## L:178 | **37 | vtx_size2 = vtx_size * 2;
+ addu $s6, $s4, $t1 ## L:180 | 38 | vtx_in_end = vtx_in0_ptr + vertices_size;
+ addiu $s7, $s7, %lo(MG_VERTEX_CACHE) ## L:181 | 39 | vtx_out0_ptr += MG_VERTEX_CACHE;
+ ori $t0, $zero, %lo(MATRICES_MVP) ## L:189 | 40 | u16 mvpaddr = MATRICES_MVP;
+ ldv $v01, 0, 0, $t0 ## L:127 | 41 | m0:sint = load(address, 0x00).xyzwxyzw;
+ ldv $v01, 8, 0, $t0 ## L:127 | 42 | m0:sint = load(address, 0x00).xyzwxyzw;
+ ldv $v02, 0, 32, $t0 ## L:128 | 43 | m0:ufract = load(address, 0x20).xyzwxyzw;
+ ldv $v02, 8, 32, $t0 ## L:128 | 44 | m0:ufract = load(address, 0x20).xyzwxyzw;
+ ldv $v03, 0, 8, $t0 ## L:129 | 45 | m1:sint = load(address, 0x08).xyzwxyzw;
+ ldv $v03, 8, 8, $t0 ## L:129 | 46 | m1:sint = load(address, 0x08).xyzwxyzw;
+ ldv $v04, 0, 40, $t0 ## L:130 | 47 | m1:ufract = load(address, 0x28).xyzwxyzw;
+ ldv $v04, 8, 40, $t0 ## L:130 | 48 | m1:ufract = load(address, 0x28).xyzwxyzw;
+ ldv $v05, 0, 16, $t0 ## L:131 | 49 | m2:sint = load(address, 0x10).xyzwxyzw;
+ ldv $v05, 8, 16, $t0 ## L:131 | 50 | m2:sint = load(address, 0x10).xyzwxyzw;
+ ldv $v06, 0, 48, $t0 ## L:132 | 51 | m2:ufract = load(address, 0x30).xyzwxyzw;
+ ldv $v06, 8, 48, $t0 ## L:132 | 52 | m2:ufract = load(address, 0x30).xyzwxyzw;
+ ldv $v07, 0, 24, $t0 ## L:133 | 53 | m3:sint = load(address, 0x18).xyzwxyzw;
+ ldv $v07, 8, 24, $t0 ## L:133 | 54 | m3:sint = load(address, 0x18).xyzwxyzw;
+ ldv $v08, 0, 56, $t0 ## L:134 | 55 | m3:ufract = load(address, 0x38).xyzwxyzw;
+ ldv $v08, 8, 56, $t0 ## L:134 | 56 | m3:ufract = load(address, 0x38).xyzwxyzw;
+ ori $at, $zero, %lo(MATRICES_NORMAL) ## L:194 | 57 | vmn0 = load(MATRICES_NORMAL, 0x00).xyzwxyzw;
+ ldv $v09, 0, 0, $at ## L:194 | 58 | vmn0 = load(MATRICES_NORMAL, 0x00).xyzwxyzw;
+ ldv $v09, 8, 0, $at ## L:194 | 59 | vmn0 = load(MATRICES_NORMAL, 0x00).xyzwxyzw;
+ ldv $v10, 0, 8, $at ## L:195 | 60 | vmn1 = load(MATRICES_NORMAL, 0x08).xyzwxyzw;
+ ldv $v10, 8, 8, $at ## L:195 | 61 | vmn1 = load(MATRICES_NORMAL, 0x08).xyzwxyzw;
+ ldv $v11, 0, 16, $at ## L:196 | 62 | vmn2 = load(MATRICES_NORMAL, 0x10).xyzwxyzw;
+ ldv $v11, 8, 16, $at ## L:196 | 63 | vmn2 = load(MATRICES_NORMAL, 0x10).xyzwxyzw;
+ ori $at, $zero, %lo(MG_NORMAL_MASK) ## L:198 | 64 | vec16 vnormmask = load(MG_NORMAL_MASK,0x0).xyzwxyzw;
+ ldv $v12, 0, 0, $at ## L:198 | 65 | vec16 vnormmask = load(MG_NORMAL_MASK,0x0).xyzwxyzw;
+ ldv $v12, 8, 0, $at ## L:198 | 66 | vec16 vnormmask = load(MG_NORMAL_MASK,0x0).xyzwxyzw;
+ ldv $v13, 0, 8, $at ## L:199 | 67 | vec16 vnormfactor = load(MG_NORMAL_MASK,0x8).xyzwxyzw;
+ ldv $v13, 8, 8, $at ## L:199 | 68 | vec16 vnormfactor = load(MG_NORMAL_MASK,0x8).xyzwxyzw;
+ ori $at, $zero, %lo(TEXTURING_SCALE) ## L:202 | 69 | vtexscale.xy = load(TEXTURING_SCALE).xy;
+ llv $v14, 0, 0, $at ## L:202 | 70 | vtexscale.xy = load(TEXTURING_SCALE).xy;
+ llv $v14, 8, 0, $at ## L:203 | 71 | vtexscale.XY = load(TEXTURING_SCALE).xy;
+ ori $at, $zero, %lo(MG_VIEWPORT) ## L:212 | 72 | vec16 vviewscale = load(MG_VIEWPORT, 0).xyzwxyzw;
+ ldv $v15, 0, 0, $at ## L:212 | 73 | vec16 vviewscale = load(MG_VIEWPORT, 0).xyzwxyzw;
+ ldv $v15, 8, 0, $at ## L:212 | 74 | vec16 vviewscale = load(MG_VIEWPORT, 0).xyzwxyzw;
+ ldv $v16, 0, 8, $at ## L:213 | 75 | vec16 vviewoff = load(MG_VIEWPORT, 8).xyzwxyzw;
+ ldv $v16, 8, 8, $at ## L:213 | 76 | vec16 vviewoff = load(MG_VIEWPORT, 8).xyzwxyzw;
+ ori $at, $zero, %lo(TEXTURING_OFFSET) ## L:216 | 77 | vtexoffset.xy = load(TEXTURING_OFFSET).xy;
+ llv $v17, 0, 0, $at ## L:216 | 78 | vtexoffset.xy = load(TEXTURING_OFFSET).xy;
+ llv $v17, 8, 0, $at ## L:217 | 79 | vtexoffset.XY = load(TEXTURING_OFFSET).xy;
+ lbu $t0, %lo(LIGHTING_HAS_POINTS + 0) ## L:219 | 80 | u8 has_points = load(LIGHTING_HAS_POINTS);
+ ori $at, $zero, %lo(FOG_PARAMS) ## L:223 | 81 | vfog_parms = load(FOG_PARAMS).xyzwxyzw;
+ ldv $v18, 0, 0, $at ## L:223 | 82 | vfog_parms = load(FOG_PARAMS).xyzwxyzw;
+ ldv $v18, 8, 0, $at ## L:223 | 83 | vfog_parms = load(FOG_PARAMS).xyzwxyzw;
+ lbu $t1, %lo(FOG_MASK + 0) ## L:224 | 84 | u8 fog_mask = load(FOG_MASK);
+ addu $t2, $s4, $t8 ## L:226 | 85 | u16 vtx_in1_ptr = vtx_in0_ptr + vtx_size;
+ addiu $t3, $s7, 40 ## L:227 | 86 | u16 vtx_out1_ptr = vtx_out0_ptr + 40;
+ ori $t4, $zero, %lo(MG_SCRATCH_MEM) ## L:229 | 87 | u16 prev_out0 = MG_SCRATCH_MEM;
+ ori $t5, $zero, %lo(MG_SCRATCH_MEM) ## L:230 | 88 | u16 prev_out1 = MG_SCRATCH_MEM;
+ LOAD_NORMAL241: lsv $v20, 0, 0, $s4 ## L:241 | 89 | @AttrLoader("NORMAL") vnorm.x = load(vtx_in0_ptr).x;
+ LOAD_NORMAL242: lsv $v20, 8, 0, $t2 ## L:242 | 90 | @AttrLoader("NORMAL") vnorm.X = load(vtx_in1_ptr).x;
+ LOAD_POSITION243: ldv $v19, 0, 0, $s4 ## L:243 | 91 | @AttrLoader("POSITION") vpos.xyzw = load(vtx_in0_ptr).xyzw;
+ LOAD_POSITION244: ldv $v19, 8, 0, $t2 ## L:244 | 92 | @AttrLoader("POSITION") vpos.XYZW = load(vtx_in1_ptr).xyzw;
+ LOAD_COLOR245: llv $v23, 0, 0, $s4 ## L:245 | 93 | @AttrLoader("COLOR") vrgba_in.xy = load(vtx_in0_ptr).xy;
+ LOAD_COLOR246: llv $v23, 4, 0, $t2 ## L:246 | 94 | @AttrLoader("COLOR") vrgba_in.zw = load(vtx_in1_ptr).xy;
+ VERTEX_LOOP:
+ sdv $v24, 0, 0, $t4 ## L:251 | **97 | @Barrier("st_c0") @Barrier("st_tr0") store(vpos_clip:sint.xyzw, prev_out0, 0); ## Barrier: 0xC
+ sdv $v24, 8, 0, $t5 ## L:252 | 98 | @Barrier("st_c1") @Barrier("st_tr1") store(vpos_clip:sint.XYZW, prev_out1, 0); ## Barrier: 0x30
+ sb $t6, 6($t4) ## L:254 | 99 | @Barrier("st_c0") store(c0, prev_out0, 6); ## Barrier: 0x4
+ sb $t7, 6($t5) ## L:255 | 100 | @Barrier("st_c1") store(c1, prev_out1, 6); ## Barrier: 0x10
+ sb $k0, 7($t4) ## L:256 | 101 | @Barrier("st_tr0") store(tr0, prev_out0, 7); ## Barrier: 0x8
+ sb $k1, 7($t5) ## L:257 | 102 | @Barrier("st_tr1") store(tr1, prev_out1, 7); ## Barrier: 0x20
+ suv $v22, 0, 8, $t4 ## L:259 | 103 | @Barrier("st_tex0") store_vec_u8(vrgba.x, prev_out0, 8); ## Barrier: 0x40
+ suv $v22, 4, 8, $t5 ## L:260 | 104 | @Barrier("st_tex1") store_vec_u8(vrgba.X, prev_out1, 8); ## Barrier: 0x80
+ slv $v21, 0, 12, $t4 ## L:262 | 105 | @Barrier("st_tex0") store(vtex.xy, prev_out0, 12); ## Barrier: 0x40
+ slv $v21, 8, 12, $t5 ## L:263 | 106 | @Barrier("st_tex1") store(vtex.XY, prev_out1, 12); ## Barrier: 0x80
+ ori $at, $zero, %lo(RSPQ_SCRATCH_MEM) ## L:265 | 107 | @Barrier("unpack_col") store(vrgba_in.xyzw, RSPQ_SCRATCH_MEM); ## Barrier: 0x100
+ sdv $v23, 0, 0, $at ## L:265 | 108 | @Barrier("unpack_col") store(vrgba_in.xyzw, RSPQ_SCRATCH_MEM); ## Barrier: 0x100
+ luv $v23, 0, 0, $at ## L:266 | 109 | @Barrier("unpack_col") vrgba_in = load_vec_u8(RSPQ_SCRATCH_MEM); ## Barrier: 0x100
+ LOAD_TEXCOORD270: llv $v21, 0, 0, $s4 ## L:270 | 110 | @AttrLoader("TEXCOORD") vtex.xy = load(vtx_in0_ptr).xy; ## Barrier: 0x7E00
+ LOAD_TEXCOORD273: llv $v21, 8, 0, $t2 ## L:273 | 111 | @AttrLoader("TEXCOORD") vtex.XY = load(vtx_in1_ptr).xy; ## Barrier: 0x7E00
+ vand $v20, $v12, $v20.h0 ## L:277 | ^ | vnorm = vnormmask & vnorm.xxxxXXXX;
+ vmudn $v20, $v20, $v13.v ## L:278 | ***115 | vnorm *= vnormfactor;
+ vmudn $v29, $v02, $v19.h0 ## L:281 | 116 | VTEMP = vmvp0 * vpos.xxxxXXXX;
+ vmadh $v29, $v01, $v19.h0 ## L:281 | 117 | VTEMP = vmvp0 * vpos.xxxxXXXX;
+ vmadn $v29, $v06, $v19.h2 ## L:282 | 118 | VTEMP = vmvp2 +* vpos.zzzzZZZZ;
+ vmadh $v29, $v05, $v19.h2 ## L:282 | 119 | VTEMP = vmvp2 +* vpos.zzzzZZZZ;
+ vmadn $v29, $v04, $v19.h1 ## L:283 | 120 | VTEMP = vmvp1 +* vpos.yyyyYYYY;
+ vmadh $v29, $v03, $v19.h1 ## L:283 | 121 | VTEMP = vmvp1 +* vpos.yyyyYYYY;
+ vmadn $v25, $v08, $v30.e7 ## L:284 | 122 | vpos_clip = vmvp3 +* 1;
+ vmadh $v24, $v07, $v30.e7 ## L:284 | 123 | vpos_clip = vmvp3 +* 1;
+ vmulf $v29, $v09, $v20.h0 ## L:286 | 124 | VTEMP = vmn0:sfract * vnorm:sfract.xxxxXXXX;
+ vmacf $v29, $v10, $v20.h1 ## L:287 | 125 | VTEMP = vmn1:sfract +* vnorm:sfract.yyyyYYYY;
+ vmacf $v20, $v11, $v20.h2 ## L:288 | 126 | vnorm = vmn2:sfract +* vnorm:sfract.zzzzZZZZ;
+ ori $at, $zero, %lo(LIGHTING_AMBIENT) ## L:290 | ^ | vrgba = load(LIGHTING_AMBIENT).xyzwxyzw;
+ ldv $v22, 0, 0, $at ## L:290 | 127 | vrgba = load(LIGHTING_AMBIENT).xyzwxyzw;
+ ldv $v22, 8, 0, $at ## L:290 | 128 | vrgba = load(LIGHTING_AMBIENT).xyzwxyzw;
+ ori $at, $zero, %lo(MG_CLIP_FACTORS) ## L:309 | 129 | vec16 vclip_factors = load(MG_CLIP_FACTORS).xyzwxyzw;
+ ldv $v26, 0, 0, $at ## L:309 | 130 | vec16 vclip_factors = load(MG_CLIP_FACTORS).xyzwxyzw;
+ ldv $v26, 8, 0, $at ## L:309 | 131 | vec16 vclip_factors = load(MG_CLIP_FACTORS).xyzwxyzw;
+ vmudn $v28, $v25, $v26.v ## L:310 | ***135 | vec32 vguard = vpos_clip * vclip_factors;
+ vmadh $v27, $v24, $v26.v ## L:310 | 136 | vec32 vguard = vpos_clip * vclip_factors;
+ vch $v29, $v27, $v27.h3 ## L:311 | ***140 | u16 clip_codes = clip(vguard, vguard.wwwwWWWW);
+ vcl $v29, $v28, $v28.h3 ## L:311 | 141 | u16 clip_codes = clip(vguard, vguard.wwwwWWWW);
+ cfc2 $fp, $vcc ## L:311 | 142 | u16 clip_codes = clip(vguard, vguard.wwwwWWWW);
+ srl $s0, $fp, 4 ## L:113 | **145 | u16 codes2 = codes >> 4;
+ andi $s1, $fp, 1799 ## L:93 | 146 | t0 = codes & 0x707;
+ srl $s2, $s1, 5 ## L:94 | 147 | t1 = t0 >> 5;
+ or $t6, $s1, $s2 ## L:101 | 148 | result = t0 | t1;
+ andi $s1, $s0, 1799 ## L:93 | 149 | t0 = codes & 0x707;
+ srl $s2, $s1, 5 ## L:94 | 150 | t1 = t0 >> 5;
+ or $t7, $s1, $s2 ## L:101 | 151 | result = t0 | t1;
+ vch $v29, $v24, $v24.h3 ## L:315 | ^ | u16 tr_codes = clip(vpos_clip, vpos_clip.wwwwWWWW);
+ vcl $v29, $v25, $v25.h3 ## L:315 | 152 | u16 tr_codes = clip(vpos_clip, vpos_clip.wwwwWWWW);
+ cfc2 $s0, $vcc ## L:315 | 153 | u16 tr_codes = clip(vpos_clip, vpos_clip.wwwwWWWW);
+ srl $s1, $s0, 4 ## L:120 | **156 | u16 codes2 = codes >> 4;
+ andi $s2, $s0, 1799 ## L:93 | 157 | t0 = codes & 0x707;
+ srl $s3, $s2, 5 ## L:94 | 158 | t1 = t0 >> 5;
+ nor $k0, $s2, $s3 ## L:108 | 159 | result = t0 ~| t1;
+ andi $s2, $s1, 1799 ## L:93 | 160 | t0 = codes & 0x707;
+ srl $s3, $s2, 5 ## L:94 | 161 | t1 = t0 >> 5;
+ nor $k1, $s2, $s3 ## L:108 | 162 | result = t0 ~| t1;
+ sdv $v24, 0, 24, $s7 ## L:319 | 163 | @Barrier("st_cp0") store(vpos_clip.xyzw, vtx_out0_ptr, 24); ## Barrier: 0x200
+ sdv $v25, 0, 32, $s7 ## L:319 | 164 | @Barrier("st_cp0") store(vpos_clip.xyzw, vtx_out0_ptr, 24); ## Barrier: 0x200
+ sdv $v24, 8, 24, $t3 ## L:320 | 165 | @Barrier("st_cp1") store(vpos_clip.XYZW, vtx_out1_ptr, 24); ## Barrier: 0x400
+ sdv $v25, 8, 32, $t3 ## L:320 | 166 | @Barrier("st_cp1") store(vpos_clip.XYZW, vtx_out1_ptr, 24); ## Barrier: 0x400
+ vaddc $v27, $v25, $v18.h1 ## L:324 | ^ | vec16 vtmp:ufract = vpos_clip:ufract + vfog_parms:ufract.yyyyYYYY;
+ vadd $v26, $v24, $v18.h0 ## L:325 | 167 | vfog:sint = vpos_clip:sint + vfog_parms:sint.xxxxXXXX;
+ vmudn $v29, $v27, $v18.h2 ## L:327 | **170 | VTEMP = vtmp:ufract * vfog_parms:sint.zzzzZZZZ;
+ vmadh $v26, $v26, $v18.h2 ## L:328 | 171 | vfog = vfog:sint +* vfog_parms:sint.zzzzZZZZ;
+ vge $v26, $v00, $v26.h2 ## L:329 | ***175 | vfog = max(VZERO, vfog.zzzzZZZZ);
+ vmudl $v25, $v25, $v15.h3 ## L:337 | 176 | vpos_clip *= vviewscale:sfract.wwwwWWWW;
+ vmadm $v24, $v24, $v15.h3 ## L:337 | 177 | vpos_clip *= vviewscale:sfract.wwwwWWWW;
+ vmadn $v25, $v00, $v00 ## L:337 | ***181 | vpos_clip *= vviewscale:sfract.wwwwWWWW;
+ ssv $v24, 6, 16, $s7 ## L:340 | ^ | @Barrier("st_w0") store(vpos_clip.w, vtx_out0_ptr, 16); ## Barrier: 0x800
+ ssv $v25, 6, 18, $s7 ## L:340 | ***185 | @Barrier("st_w0") store(vpos_clip.w, vtx_out0_ptr, 16); ## Barrier: 0x800
+ ssv $v24, 14, 16, $t3 ## L:341 | 186 | @Barrier("st_w1") store(vpos_clip.W, vtx_out1_ptr, 16); ## Barrier: 0x1000
+ ssv $v25, 14, 18, $t3 ## L:341 | 187 | @Barrier("st_w1") store(vpos_clip.W, vtx_out1_ptr, 16); ## Barrier: 0x1000
+ vrcph $v27.e3, $v24.e3 ## L:344 | ^ | vinvw.w = invert_half(vpos_clip).w;
+ vrcpl $v28.e3, $v25.e3 ## L:344 | 188 | vinvw.w = invert_half(vpos_clip).w;
+ vrcph $v27.e3, $v24.e7 ## L:345 | 189 | vinvw.W = invert_half(vpos_clip).W;
+ vrcpl $v28.e7, $v25.e7 ## L:345 | 190 | vinvw.W = invert_half(vpos_clip).W;
+ vrcph $v27.e7, $v00.e7 ## L:345 | 191 | vinvw.W = invert_half(vpos_clip).W;
+ ssv $v27, 6, 20, $s7 ## L:347 | ***195 | @Barrier("st_iw0") store(vinvw.w, vtx_out0_ptr, 20); ## Barrier: 0x2000
+ ssv $v28, 6, 22, $s7 ## L:347 | 196 | @Barrier("st_iw0") store(vinvw.w, vtx_out0_ptr, 20); ## Barrier: 0x2000
+ ssv $v27, 14, 20, $t3 ## L:348 | 197 | @Barrier("st_iw1") store(vinvw.W, vtx_out1_ptr, 20); ## Barrier: 0x4000
+ ssv $v28, 14, 22, $t3 ## L:348 | 198 | @Barrier("st_iw1") store(vinvw.W, vtx_out1_ptr, 20); ## Barrier: 0x4000
+ vmudl $v29, $v25, $v28.h3 ## L:350 | ^ | vpos_clip *= vinvw.wwwwWWWW;
+ vmadm $v29, $v24, $v28.h3 ## L:350 | 199 | vpos_clip *= vinvw.wwwwWWWW;
+ vmadn $v25, $v25, $v27.h3 ## L:350 | 200 | vpos_clip *= vinvw.wwwwWWWW;
+ vmadh $v24, $v24, $v27.h3 ## L:350 | 201 | vpos_clip *= vinvw.wwwwWWWW;
+ vmudh $v29, $v16, $v30.e7 ## L:354 | 202 | VTEMP = vviewoff:sint * 1;
+ vmadn $v25, $v25, $v15.v ## L:355 | *204 | vpos_clip = vpos_clip +* vviewscale;
+ vmadh $v24, $v24, $v15.v ## L:355 | 205 | vpos_clip = vpos_clip +* vviewscale;
+ beq $t0, $zero, LABEL_mgfx_0001 ## L:363 | ^ | if (has_points != 0)
+ lbu $sp, %lo(LIGHTING_COUNT + 0) ## L:291 | *207 | u8 light_count = load(LIGHTING_COUNT);
+ ori $fp, $zero, %lo(MATRICES_MV) ## L:366 | 208 | u16 addr = MATRICES_MV;
+ ldv $v01, 0, 0, $fp ## L:127 | 209 | m0:sint = load(address, 0x00).xyzwxyzw;
+ ldv $v01, 8, 0, $fp ## L:127 | 210 | m0:sint = load(address, 0x00).xyzwxyzw;
+ ldv $v02, 0, 32, $fp ## L:128 | 211 | m0:ufract = load(address, 0x20).xyzwxyzw;
+ ldv $v02, 8, 32, $fp ## L:128 | 212 | m0:ufract = load(address, 0x20).xyzwxyzw;
+ ldv $v03, 0, 8, $fp ## L:129 | 213 | m1:sint = load(address, 0x08).xyzwxyzw;
+ ldv $v03, 8, 8, $fp ## L:129 | 214 | m1:sint = load(address, 0x08).xyzwxyzw;
+ ldv $v04, 0, 40, $fp ## L:130 | 215 | m1:ufract = load(address, 0x28).xyzwxyzw;
+ ldv $v04, 8, 40, $fp ## L:130 | 216 | m1:ufract = load(address, 0x28).xyzwxyzw;
+ ldv $v05, 0, 16, $fp ## L:131 | 217 | m2:sint = load(address, 0x10).xyzwxyzw;
+ ldv $v05, 8, 16, $fp ## L:131 | 218 | m2:sint = load(address, 0x10).xyzwxyzw;
+ ldv $v06, 0, 48, $fp ## L:132 | 219 | m2:ufract = load(address, 0x30).xyzwxyzw;
+ ldv $v06, 8, 48, $fp ## L:132 | 220 | m2:ufract = load(address, 0x30).xyzwxyzw;
+ ldv $v07, 0, 24, $fp ## L:133 | 221 | m3:sint = load(address, 0x18).xyzwxyzw;
+ ldv $v07, 8, 24, $fp ## L:133 | 222 | m3:sint = load(address, 0x18).xyzwxyzw;
+ ldv $v08, 0, 56, $fp ## L:134 | 223 | m3:ufract = load(address, 0x38).xyzwxyzw;
+ ldv $v08, 8, 56, $fp ## L:134 | 224 | m3:ufract = load(address, 0x38).xyzwxyzw;
+ vmudn $v29, $v02, $v19.h0 ## L:369 | ^ | VTEMP = vmvp0 * vpos.xxxxXXXX;
+ vmadh $v29, $v01, $v19.h0 ## L:369 | 225 | VTEMP = vmvp0 * vpos.xxxxXXXX;
+ vmadn $v29, $v04, $v19.h1 ## L:370 | 226 | VTEMP = vmvp1 +* vpos.yyyyYYYY;
+ vmadh $v29, $v03, $v19.h1 ## L:370 | 227 | VTEMP = vmvp1 +* vpos.yyyyYYYY;
+ vmadn $v29, $v06, $v19.h2 ## L:371 | 228 | VTEMP = vmvp2 +* vpos.zzzzZZZZ;
+ vmadh $v29, $v05, $v19.h2 ## L:371 | 229 | VTEMP = vmvp2 +* vpos.zzzzZZZZ;
+ vmadn $v29, $v08, $v30.e7 ## L:372 | 230 | vpos = vmvp3 +* 1;
+ vmadh $v19, $v07, $v30.e7 ## L:372 | 231 | vpos = vmvp3 +* 1;
+ LABEL_mgfx_0001:
+ beq $sp, $zero, LIGHT_END ## L:376 | ^ | if (light_count == 0) goto LIGHT_END;
+ ori $fp, $zero, %lo(LIGHTING_LIGHTS) ## L:375 | *233 | u16 light_ptr = LIGHTING_LIGHTS;
+ LIGHT_LOOP:
+ ldv $v27, 0, 8, $fp ## L:379 | 234 | vec16 vlcol = load(light_ptr, 8).xyzwxyzw;
+ ldv $v28, 0, 0, $fp ## L:380 | 235 | vec16 vlpos = load(light_ptr, 0).xyzwxyzw;
+ ldv $v28, 8, 0, $fp ## L:380 | 236 | vec16 vlpos = load(light_ptr, 0).xyzwxyzw;
+ lh $s0, 6($fp) ## L:382 | 237 | s16 light_w = load(light_ptr, 6);
+ beq $s0, $zero, LABEL_mgfx_0003 ## L:383 | **240 | if (light_w != 0)
+ ldv $v27, 8, 8, $fp ## L:379 | *242 | vec16 vlcol = load(light_ptr, 8).xyzwxyzw;
+ vsubc $v28, $v28, $v19.v ## L:391 | 243 | vlpos -= vpos;
+ vmudh $v02, $v28, $v28.v ## L:392 | ***247 | vec32 vsqdist = vlpos * vlpos;
+ vsar $v01, COP2_ACC_HI ## L:392 | 248 | vec32 vsqdist = vlpos * vlpos;
+ vsar $v02, COP2_ACC_MD ## L:392 | 249 | vec32 vsqdist = vlpos * vlpos;
+ vaddc $v04, $v02, $v02.h1 ## L:393 | ***253 | vec32 vtmp = vsqdist + vsqdist.yyyyYYYY;
+ vadd $v03, $v01, $v01.h1 ## L:393 | 254 | vec32 vtmp = vsqdist + vsqdist.yyyyYYYY;
+ vaddc $v02, $v04, $v02.h2 ## L:394 | **257 | vsqdist = vtmp + vsqdist.zzzzZZZZ;
+ vadd $v01, $v03, $v01.h2 ## L:394 | 258 | vsqdist = vtmp + vsqdist.zzzzZZZZ;
+ vrsqh $v03.e0, $v01.e0 ## L:396 | ***262 | vtmp.x = invert_half_sqrt(vsqdist).x;
+ vrsql $v04.e0, $v02.e0 ## L:396 | 263 | vtmp.x = invert_half_sqrt(vsqdist).x;
+ vrsqh $v03.e0, $v01.e4 ## L:397 | 264 | vtmp.X = invert_half_sqrt(vsqdist).X;
+ vrsql $v04.e4, $v02.e4 ## L:397 | 265 | vtmp.X = invert_half_sqrt(vsqdist).X;
+ vrsqh $v03.e4, $v00.e0 ## L:397 | 266 | vtmp.X = invert_half_sqrt(vsqdist).X;
+ vmudm $v29, $v28, $v04.h0 ## L:399 | **269 | vlpos *= vtmp.xxxxXXXX;
+ vmadh $v28, $v28, $v03.h0 ## L:399 | 270 | vlpos *= vtmp.xxxxXXXX;
+ vmudn $v29, $v04, $v27.h3 ## L:400 | 271 | vec16 vatten = vtmp * vlcol.wwwwWWWW;
+ vmadh $v05, $v03, $v27.h3 ## L:400 | 272 | vec16 vatten = vtmp * vlcol.wwwwWWWW;
+ vmulf $v05, $v05, $v05.v ## L:401 | ***276 | vatten:sfract *= vatten:sfract;
+ vmulf $v27, $v27, $v05.h0 ## L:402 | ***280 | vlcol:sfract *= vatten:sfract.xxxxXXXX;
+ ori $s1, $zero, %lo(MATRICES_MVP) ## L:411 | ^ | u16 addr = MATRICES_MVP;
+ ldv $v01, 0, 0, $s1 ## L:127 | 281 | m0:sint = load(address, 0x00).xyzwxyzw;
+ ldv $v01, 8, 0, $s1 ## L:127 | 282 | m0:sint = load(address, 0x00).xyzwxyzw;
+ ldv $v02, 0, 32, $s1 ## L:128 | 283 | m0:ufract = load(address, 0x20).xyzwxyzw;
+ ldv $v02, 8, 32, $s1 ## L:128 | 284 | m0:ufract = load(address, 0x20).xyzwxyzw;
+ ldv $v03, 0, 8, $s1 ## L:129 | 285 | m1:sint = load(address, 0x08).xyzwxyzw;
+ ldv $v03, 8, 8, $s1 ## L:129 | 286 | m1:sint = load(address, 0x08).xyzwxyzw;
+ ldv $v04, 0, 40, $s1 ## L:130 | 287 | m1:ufract = load(address, 0x28).xyzwxyzw;
+ ldv $v04, 8, 40, $s1 ## L:130 | 288 | m1:ufract = load(address, 0x28).xyzwxyzw;
+ ldv $v05, 0, 16, $s1 ## L:131 | 289 | m2:sint = load(address, 0x10).xyzwxyzw;
+ ldv $v05, 8, 16, $s1 ## L:131 | 290 | m2:sint = load(address, 0x10).xyzwxyzw;
+ ldv $v06, 0, 48, $s1 ## L:132 | 291 | m2:ufract = load(address, 0x30).xyzwxyzw;
+ ldv $v06, 8, 48, $s1 ## L:132 | 292 | m2:ufract = load(address, 0x30).xyzwxyzw;
+ ldv $v07, 0, 24, $s1 ## L:133 | 293 | m3:sint = load(address, 0x18).xyzwxyzw;
+ ldv $v07, 8, 24, $s1 ## L:133 | 294 | m3:sint = load(address, 0x18).xyzwxyzw;
+ ldv $v08, 0, 56, $s1 ## L:134 | 295 | m3:ufract = load(address, 0x38).xyzwxyzw;
+ ldv $v08, 8, 56, $s1 ## L:134 | 296 | m3:ufract = load(address, 0x38).xyzwxyzw;
+ LABEL_mgfx_0003:
+ vmulf $v28, $v28, $v20.v ## L:416 | ^ | vlpos *= vnorm:sfract;
+ vmulf $v29, $v27, $v28.h0 ## L:417 | ***300 | VTEMP = vlcol:sfract * vlpos:sfract.xxxxXXXX;
+ vmacf $v29, $v27, $v28.h1 ## L:418 | 301 | VTEMP = vlcol:sfract +* vlpos:sfract.yyyyYYYY;
+ vmacf $v27, $v27, $v28.h2 ## L:419 | 302 | vlcol = vlcol:sfract +* vlpos:sfract.zzzzZZZZ;
+ vge $v27, $v27, $v00 ## L:420 | ***306 | vlcol = max(vlcol, VZERO);
+ addiu $fp, $fp, 16 ## L:424 | ^ | light_ptr += 16;
+ addiu $sp, $sp, 65535 ## L:425 | 307 | light_count -= 1;
+ bne $sp, $zero, LIGHT_LOOP ## L:426 | 308 | if (light_count != 0) goto LIGHT_LOOP;
+ vadd $v22, $v22, $v27.v ## L:422 | **311 | vrgba:sint += vlcol;
+ LIGHT_END:
+ vmov $v22.e3, $v12.e3 ## L:430 | 312 | vrgba.w = vnormmask.w;
+ vmov $v22.e7, $v12.e3 ## L:431 | 313 | vrgba.W = vnormmask.w;
+ PATCH_COLOR432: vmulf $v22, $v22, $v23.v ## L:432 | ***317 | @AttrPatch("COLOR:vnop") vrgba:sfract *= vrgba_in:sfract;
+ ctc2 $t1, $vcc ## L:434 | ^ | set_vcc(fog_mask);
+ vmrg $v22, $v22, $v26.h2 ## L:435 | ***321 | vrgba = select(vrgba, vfog.zzzzZZZZ);
+ vmudh $v29, $v17, $v30.e7 ## L:493 | 322 | VTEMP = vtexoffset:sint * 1;
+ or $t4, $zero, $s7 ## L:496 | ^ | prev_out0 = vtx_out0_ptr;
+ or $t5, $zero, $t3 ## L:497 | 323 | prev_out1 = vtx_out1_ptr;
+ addu $s4, $s4, $t9 ## L:498 | 324 | vtx_in0_ptr += vtx_size2;
+ addu $t2, $t2, $t9 ## L:499 | 325 | vtx_in1_ptr += vtx_size2;
+ addiu $s7, $s7, 80 ## L:500 | 326 | vtx_out0_ptr += 80;
+ addiu $t3, $t3, 80 ## L:501 | 327 | vtx_out1_ptr += 80;
+ LOAD_NORMAL503: lsv $v20, 0, 0, $s4 ## L:503 | 328 | @AttrLoader("NORMAL") vnorm.x = load(vtx_in0_ptr).x;
+ LOAD_NORMAL504: lsv $v20, 8, 0, $t2 ## L:504 | 329 | @AttrLoader("NORMAL") vnorm.X = load(vtx_in1_ptr).x;
+ LOAD_POSITION505: ldv $v19, 0, 0, $s4 ## L:505 | 330 | @AttrLoader("POSITION") vpos.xyzw = load(vtx_in0_ptr).xyzw;
+ LOAD_POSITION506: ldv $v19, 8, 0, $t2 ## L:506 | 331 | @AttrLoader("POSITION") vpos.XYZW = load(vtx_in1_ptr).xyzw;
+ LOAD_COLOR507: llv $v23, 0, 0, $s4 ## L:507 | 332 | @AttrLoader("COLOR") vrgba_in.xy = load(vtx_in0_ptr).xy;
+ LOAD_COLOR508: llv $v23, 4, 0, $t2 ## L:508 | 333 | @AttrLoader("COLOR") vrgba_in.zw = load(vtx_in1_ptr).xy;
+ sltu $at, $s4, $s6 ## L:510 | 334 | if (vtx_in0_ptr < vtx_in_end) goto VERTEX_LOOP;
+ bne $at, $zero, VERTEX_LOOP ## L:510 | 335 | if (vtx_in0_ptr < vtx_in_end) goto VERTEX_LOOP;
+ vmadh $v21, $v21, $v14.v ## L:494 | *337 | vtex:sint = vtex:sint +* vtexscale;
+ sdv $v24, 0, 0, $t4 ## L:512 | 338 | @Barrier("st_c0") @Barrier("st_tr0") store(vpos_clip:sint.xyzw, prev_out0, 0); ## Barrier: 0xC
+ sdv $v24, 8, 0, $t5 ## L:513 | 339 | @Barrier("st_c1") @Barrier("st_tr1") store(vpos_clip:sint.XYZW, prev_out1, 0); ## Barrier: 0x30
+ sb $t7, 6($t5) ## L:516 | 340 | @Barrier("st_c1") store(c1, prev_out1, 6); ## Barrier: 0x10
+ sb $k0, 7($t4) ## L:517 | 341 | @Barrier("st_tr0") store(tr0, prev_out0, 7); ## Barrier: 0x8
+ sb $k1, 7($t5) ## L:518 | 342 | @Barrier("st_tr1") store(tr1, prev_out1, 7); ## Barrier: 0x20
+ suv $v22, 0, 8, $t4 ## L:520 | 343 | @Barrier("st_tex0") store_vec_u8(vrgba.x, prev_out0, 8); ## Barrier: 0x40
+ suv $v22, 4, 8, $t5 ## L:521 | 344 | @Barrier("st_tex1") store_vec_u8(vrgba.X, prev_out1, 8); ## Barrier: 0x80
+ slv $v21, 0, 12, $t4 ## L:523 | 345 | @Barrier("st_tex0") store(vtex.xy, prev_out0, 12); ## Barrier: 0x40
+ slv $v21, 8, 12, $t5 ## L:524 | 346 | @Barrier("st_tex1") store(vtex.XY, prev_out1, 12); ## Barrier: 0x80
+ j RSPQ_Loop ## L:525 | 347 | }
+ sb $t6, 6($t4) ## L:515 | *349 | @Barrier("st_c0") store(c0, prev_out0, 6); ## Barrier: 0x4
+
+MgEndShader
+
+#define zero $0
+#define v0 $2
+#define v1 $3
+#define a0 $4
+#define a1 $5
+#define a2 $6
+#define a3 $7
+#define t0 $8
+#define t1 $9
+#define t2 $10
+#define t3 $11
+#define t4 $12
+#define t5 $13
+#define t6 $14
+#define t7 $15
+#define s0 $16
+#define s1 $17
+#define s2 $18
+#define s3 $19
+#define s4 $20
+#define s5 $21
+#define s6 $22
+#define s7 $23
+#define t8 $24
+#define t9 $25
+#define k0 $26
+#define k1 $27
+#define gp $28
+#define sp $29
+#define fp $30
+#define ra $31
+
+.set at
+.set macro
\ No newline at end of file
diff --git a/src/tests/examples/rsp_mgfx.rspl b/src/tests/examples/rsp_mgfx.rspl
new file mode 100644
index 0000000..2d27231
--- /dev/null
+++ b/src/tests/examples/rsp_mgfx.rspl
@@ -0,0 +1,526 @@
+/*
+ * rsp_mgfx.rspl
+ *
+ * Authors: Dennis Heinze
+ *
+ */
+
+include "rsp_magma.inc"
+
+#define MG_VTX_XYZ 0
+#define MG_VTX_CLIP_CODE 6
+#define MG_VTX_TR_CODE 7
+#define MG_VTX_RGBA 8
+#define MG_VTX_ST 12
+#define MG_VTX_Wi 16
+#define MG_VTX_Wf 18
+#define MG_VTX_INVWi 20
+#define MG_VTX_INVWf 22
+#define MG_VTX_CS_POSi 24
+#define MG_VTX_CS_POSf 32
+#define MG_VTX_SIZE 40
+#define MG_VTX_SIZE2 80
+
+#define MGFX_ATTRIBUTE_POSITION 0
+#define MGFX_ATTRIBUTE_NORMAL 1
+#define MGFX_ATTRIBUTE_COLOR 2
+#define MGFX_ATTRIBUTE_TEXCOORD 3
+#define MGFX_BINDING_MATRICES 0
+#define MGFX_BINDING_TEXTURING 1
+#define MGFX_BINDING_LIGHTING 2
+#define MGFX_BINDING_FOG 3
+#define MGFX_LIGHT_COUNT_MAX 8
+#define MGFX_LIGHT_POSITION 0
+#define MGFX_LIGHT_POSITIONW 6
+#define MGFX_LIGHT_COLOR 8
+#define MGFX_LIGHT_INTENSITY 14
+#define MGFX_LIGHT_SIZE 16
+#define MGFX_MATRIX_SIZE 64
+#define MGFX_VTX_TEX_SHIFT 5
+
+#define SCRATCH_TEXSCALE 0x80
+
+state
+{
+ extern u16 RSPQ_SCRATCH_MEM;
+ extern u16 MG_SCRATCH_MEM;
+
+ extern vec16 MG_NORMAL_MASK[8];
+
+ extern u16 MG_VERTEX_CACHE;
+ extern u16 MG_VERTEX_CACHE_END;
+
+ extern vec16 MG_VIEWPORT[8];
+ extern vec16 MG_CLIP_FACTORS[4];
+ extern vec16 MG_VERTEX_SIZE[4];
+ extern u32 MG_VERTEX_BUFFER;
+}
+
+uniform MATRICES
+{
+ vec16 MATRICES_MVP[4];
+ vec16 MATRICES_MV[4];
+ vec16 MATRICES_NORMAL[2];
+}
+
+uniform TEXTURING
+{
+ s16 TEXTURING_SCALE[2];
+ s16 TEXTURING_OFFSET[2];
+}
+
+uniform LIGHTING
+{
+ u8 LIGHTING_LIGHTS[MGFX_LIGHT_SIZE][MGFX_LIGHT_COUNT_MAX];
+ s16 LIGHTING_AMBIENT[3];
+ u8 LIGHTING_HAS_POINTS;
+ u8 LIGHTING_COUNT;
+}
+
+uniform FOG
+{
+ s16 FOG_PARAMS[3];
+ u8 FOG_MASK;
+}
+
+attribute s16 POSITION[3];
+attribute s16 NORMAL?;
+attribute u32 COLOR?;
+attribute s16 TEXCOORD[2]?;
+
+macro __pack_clip_codes(u16 codes, u16 t0, u16 t1)
+{
+ t0 = codes & 0x707;
+ t1 = t0 >> 5;
+}
+
+macro pack_clip_codes(u8 result, u16 codes)
+{
+ u16 t0, t1;
+ __pack_clip_codes(codes, t0, t1);
+ result = t0 | t1;
+}
+
+macro pack_clip_codes_inv(u8 result, u16 codes)
+{
+ u16 t0, t1;
+ __pack_clip_codes(codes, t0, t1);
+ result = t0 ~| t1;
+}
+
+macro pack_clip_codes2(u8 result0, u8 result1, u16 codes)
+{
+ u16 codes2 = codes >> 4;
+ pack_clip_codes(result0, codes);
+ pack_clip_codes(result1, codes2);
+}
+
+macro pack_clip_codes2_inv(u8 result0, u8 result1, u16 codes)
+{
+ u16 codes2 = codes >> 4;
+ pack_clip_codes_inv(result0, codes);
+ pack_clip_codes_inv(result1, codes2);
+}
+
+macro load_mat4x4(vec32 m0, vec32 m1, vec32 m2, vec32 m3, u16 address)
+{
+ m0:sint = load(address, 0x00).xyzwxyzw;
+ m0:ufract = load(address, 0x20).xyzwxyzw;
+ m1:sint = load(address, 0x08).xyzwxyzw;
+ m1:ufract = load(address, 0x28).xyzwxyzw;
+ m2:sint = load(address, 0x10).xyzwxyzw;
+ m2:ufract = load(address, 0x30).xyzwxyzw;
+ m3:sint = load(address, 0x18).xyzwxyzw;
+ m3:ufract = load(address, 0x38).xyzwxyzw;
+}
+
+shader mgfx()
+{
+ u16<$t8> vtx_size;
+ u16<$t9> vtx_size2;
+ u16<$s4> vtx_in0_ptr;
+ u16<$s6> vtx_in_end;
+ u16<$s7> vtx_out0_ptr;
+
+ {
+ u32 inputs_ptr = get_cmd_address(0x2);
+ vec16 vvtx_size = load(MG_VERTEX_SIZE).xyzw;
+ vec16 vinputs = load(inputs_ptr).xyzw;
+
+ vec32 vproducts = vinputs * vvtx_size;
+ @Barrier("inputs0") store(vproducts:ufract.xyzw, RSPQ_SCRATCH_MEM);
+ @Barrier("inputs1") store(vproducts.x, RSPQ_SCRATCH_MEM, 0x8);
+ }
+
+ {
+ s16<$t0> dma_size;
+
+ @Barrier("inputs0") vtx_out0_ptr = load(RSPQ_SCRATCH_MEM, 0x2);
+ u16 vertices_size;
+ @Barrier("inputs0") vertices_size = load(RSPQ_SCRATCH_MEM, 0x4);
+ u32 buffer_offset;
+ @Barrier("inputs1") buffer_offset = load(RSPQ_SCRATCH_MEM, 0x8);
+
+ u32<$s0> vtx_buffer = load(MG_VERTEX_BUFFER);
+ vtx_buffer += buffer_offset;
+
+ u32 misalignment = buffer_offset & 0x7;
+ dma_size = vertices_size + misalignment;
+ dma_size += 0x7;
+ dma_size &= 0xFF8;
+
+ vtx_in0_ptr = MG_VERTEX_CACHE_END;
+ vtx_in0_ptr -= dma_size;
+
+ dma_in(vtx_in0_ptr, vtx_buffer, dma_size);
+
+ vtx_size = load(MG_VERTEX_SIZE);
+ vtx_size2 = vtx_size * 2;
+
+ vtx_in_end = vtx_in0_ptr + vertices_size;
+ vtx_out0_ptr += MG_VERTEX_CACHE;
+ }
+
+ vec32<$v01> vmvp0;
+ vec32<$v03> vmvp1;
+ vec32<$v05> vmvp2;
+ vec32<$v07> vmvp3;
+ {
+ u16 mvpaddr = MATRICES_MVP;
+ load_mat4x4(vmvp0, vmvp1, vmvp2, vmvp3, mvpaddr);
+ }
+
+ vec16 vmn0, vmn1, vmn2;
+ vmn0 = load(MATRICES_NORMAL, 0x00).xyzwxyzw;
+ vmn1 = load(MATRICES_NORMAL, 0x08).xyzwxyzw;
+ vmn2 = load(MATRICES_NORMAL, 0x10).xyzwxyzw;
+
+ vec16 vnormmask = load(MG_NORMAL_MASK,0x0).xyzwxyzw;
+ vec16 vnormfactor = load(MG_NORMAL_MASK,0x8).xyzwxyzw;
+
+ vec16 vtexscale;
+ vtexscale.xy = load(TEXTURING_SCALE).xy;
+ vtexscale.XY = load(TEXTURING_SCALE).xy;
+
+ #ifdef ENABLE_ENV_MAP
+ {
+ vec32 vtexscale2 = vtexscale >> 3;
+ store(vtexscale2, MG_SCRATCH_MEM, SCRATCH_TEXSCALE);
+ }
+ undef vtexscale;
+ #else
+ vec16 vviewscale = load(MG_VIEWPORT, 0).xyzwxyzw;
+ vec16 vviewoff = load(MG_VIEWPORT, 8).xyzwxyzw;
+
+ vec16 vtexoffset;
+ vtexoffset.xy = load(TEXTURING_OFFSET).xy;
+ vtexoffset.XY = load(TEXTURING_OFFSET).xy;
+
+ u8 has_points = load(LIGHTING_HAS_POINTS);
+ #endif
+
+ vec16 vfog_parms;
+ vfog_parms = load(FOG_PARAMS).xyzwxyzw;
+ u8 fog_mask = load(FOG_MASK);
+
+ u16 vtx_in1_ptr = vtx_in0_ptr + vtx_size;
+ u16 vtx_out1_ptr = vtx_out0_ptr + MG_VTX_SIZE;
+
+ u16 prev_out0 = MG_SCRATCH_MEM;
+ u16 prev_out1 = MG_SCRATCH_MEM;
+
+ u8 c0, c1, tr0, tr1;
+
+ vec16 vpos;
+ vec16 vnorm;
+ vec16 vtex;
+ vec16 vrgba;
+ vec16 vrgba_in;
+ vec32 vpos_clip;
+
+ @AttrLoader("NORMAL") vnorm.x = load(vtx_in0_ptr).x;
+ @AttrLoader("NORMAL") vnorm.X = load(vtx_in1_ptr).x;
+ @AttrLoader("POSITION") vpos.xyzw = load(vtx_in0_ptr).xyzw;
+ @AttrLoader("POSITION") vpos.XYZW = load(vtx_in1_ptr).xyzw;
+ @AttrLoader("COLOR") vrgba_in.xy = load(vtx_in0_ptr).xy;
+ @AttrLoader("COLOR") vrgba_in.zw = load(vtx_in1_ptr).xy;
+
+ {
+ VERTEX_LOOP:
+
+ @Barrier("st_c0") @Barrier("st_tr0") store(vpos_clip:sint.xyzw, prev_out0, MG_VTX_XYZ);
+ @Barrier("st_c1") @Barrier("st_tr1") store(vpos_clip:sint.XYZW, prev_out1, MG_VTX_XYZ);
+
+ @Barrier("st_c0") store(c0, prev_out0, MG_VTX_CLIP_CODE);
+ @Barrier("st_c1") store(c1, prev_out1, MG_VTX_CLIP_CODE);
+ @Barrier("st_tr0") store(tr0, prev_out0, MG_VTX_TR_CODE);
+ @Barrier("st_tr1") store(tr1, prev_out1, MG_VTX_TR_CODE);
+
+ @Barrier("st_tex0") store_vec_u8(vrgba.x, prev_out0, MG_VTX_RGBA);
+ @Barrier("st_tex1") store_vec_u8(vrgba.X, prev_out1, MG_VTX_RGBA);
+
+ @Barrier("st_tex0") store(vtex.xy, prev_out0, MG_VTX_ST);
+ @Barrier("st_tex1") store(vtex.XY, prev_out1, MG_VTX_ST);
+
+ @Barrier("unpack_col") store(vrgba_in.xyzw, RSPQ_SCRATCH_MEM);
+ @Barrier("unpack_col") vrgba_in = load_vec_u8(RSPQ_SCRATCH_MEM);
+
+ #ifndef ENABLE_ENV_MAP
+ @Barrier("st_cp0") @Barrier("st_cp1") @Barrier("st_w0") @Barrier("st_w1") @Barrier("st_iw0") @Barrier("st_iw1")
+ @AttrLoader("TEXCOORD") vtex.xy = load(vtx_in0_ptr).xy;
+
+ @Barrier("st_cp0") @Barrier("st_cp1") @Barrier("st_w0") @Barrier("st_w1") @Barrier("st_iw0") @Barrier("st_iw1")
+ @AttrLoader("TEXCOORD") vtex.XY = load(vtx_in1_ptr).xy;
+ #endif
+
+ // View space normals
+ vnorm = vnormmask & vnorm.xxxxXXXX;
+ vnorm *= vnormfactor;
+
+ // Clip space position
+ VTEMP = vmvp0 * vpos.xxxxXXXX;
+ VTEMP = vmvp2 +* vpos.zzzzZZZZ;
+ VTEMP = vmvp1 +* vpos.yyyyYYYY;
+ vpos_clip = vmvp3 +* 1;
+
+ VTEMP = vmn0:sfract * vnorm:sfract.xxxxXXXX;
+ VTEMP = vmn1:sfract +* vnorm:sfract.yyyyYYYY;
+ vnorm = vmn2:sfract +* vnorm:sfract.zzzzZZZZ;
+
+ vrgba = load(LIGHTING_AMBIENT).xyzwxyzw;
+ u8 light_count = load(LIGHTING_COUNT);
+
+ #ifdef ENABLE_NORMALIZE
+ {
+ vec32 vsqdist = vnorm * vnorm;
+ vec32 vtmp = vsqdist + vsqdist.yyyyYYYY;
+ vsqdist = vtmp + vsqdist.zzzzZZZZ;
+
+ vec32 vinvdist;
+ vinvdist.x = invert_half_sqrt(vsqdist).x;
+ vinvdist.X = invert_half_sqrt(vsqdist).X;
+
+ vnorm *= vinvdist.xxxxXXXX;
+ }
+ #endif
+
+ { // Clip codes
+
+ vec16 vclip_factors = load(MG_CLIP_FACTORS).xyzwxyzw;
+ vec32 vguard = vpos_clip * vclip_factors;
+ u16 clip_codes = clip(vguard, vguard.wwwwWWWW);
+ pack_clip_codes2(c0, c1, clip_codes);
+
+ // Trivial reject codes
+ u16 tr_codes = clip(vpos_clip, vpos_clip.wwwwWWWW);
+ pack_clip_codes2_inv(tr0, tr1, tr_codes);
+ }
+
+ @Barrier("st_cp0") store(vpos_clip.xyzw, vtx_out0_ptr, MG_VTX_CS_POSi);
+ @Barrier("st_cp1") store(vpos_clip.XYZW, vtx_out1_ptr, MG_VTX_CS_POSi);
+
+ vec16 vfog;
+ { // Fog
+ vec16 vtmp:ufract = vpos_clip:ufract + vfog_parms:ufract.yyyyYYYY;
+ vfog:sint = vpos_clip:sint + vfog_parms:sint.xxxxXXXX;
+
+ VTEMP = vtmp:ufract * vfog_parms:sint.zzzzZZZZ;
+ vfog = vfog:sint +* vfog_parms:sint.zzzzZZZZ;
+ vfog = max(VZERO, vfog.zzzzZZZZ);
+ }
+
+ #ifdef ENABLE_ENV_MAP
+ vec16 vviewscale = load(MG_VIEWPORT, 0).xyzwxyzw;
+ vec16 vviewoff = load(MG_VIEWPORT, 8).xyzwxyzw;
+ #endif
+
+ vpos_clip *= vviewscale:sfract.wwwwWWWW;
+
+ // Screen space position + normalized W
+ @Barrier("st_w0") store(vpos_clip.w, vtx_out0_ptr, MG_VTX_Wi);
+ @Barrier("st_w1") store(vpos_clip.W, vtx_out1_ptr, MG_VTX_Wi);
+
+ vec32 vinvw;
+ vinvw.w = invert_half(vpos_clip).w;
+ vinvw.W = invert_half(vpos_clip).W;
+
+ @Barrier("st_iw0") store(vinvw.w, vtx_out0_ptr, MG_VTX_INVWi);
+ @Barrier("st_iw1") store(vinvw.W, vtx_out1_ptr, MG_VTX_INVWi);
+
+ vpos_clip *= vinvw.wwwwWWWW;
+
+ undef vinvw;
+
+ VTEMP = vviewoff:sint * 1;
+ vpos_clip = vpos_clip +* vviewscale;
+
+ #ifdef ENABLE_ENV_MAP
+ undef vviewscale;
+ undef vviewoff;
+ #endif
+
+ #ifndef ENABLE_ENV_MAP
+ if (has_points != 0)
+ #endif
+ {
+ u16 addr = MATRICES_MV;
+ load_mat4x4(vmvp0, vmvp1, vmvp2, vmvp3, addr);
+
+ VTEMP = vmvp0 * vpos.xxxxXXXX;
+ VTEMP = vmvp1 +* vpos.yyyyYYYY;
+ VTEMP = vmvp2 +* vpos.zzzzZZZZ;
+ vpos = vmvp3 +* 1;
+ }
+
+ u16 light_ptr = LIGHTING_LIGHTS;
+ if (light_count == 0) goto LIGHT_END;
+ {
+ LIGHT_LOOP:
+ vec16 vlcol = load(light_ptr, MGFX_LIGHT_COLOR).xyzwxyzw;
+ vec16 vlpos = load(light_ptr, MGFX_LIGHT_POSITION).xyzwxyzw;
+
+ s16 light_w = load(light_ptr, MGFX_LIGHT_POSITIONW);
+ if (light_w != 0)
+ {
+ undef vmvp0;
+ undef vmvp1;
+ undef vmvp2;
+ undef vmvp3;
+
+ {
+ vlpos -= vpos;
+ vec32 vsqdist = vlpos * vlpos;
+ vec32 vtmp = vsqdist + vsqdist.yyyyYYYY;
+ vsqdist = vtmp + vsqdist.zzzzZZZZ;
+
+ vtmp.x = invert_half_sqrt(vsqdist).x;
+ vtmp.X = invert_half_sqrt(vsqdist).X;
+
+ vlpos *= vtmp.xxxxXXXX;
+ vec16 vatten = vtmp * vlcol.wwwwWWWW;
+ vatten:sfract *= vatten:sfract;
+ vlcol:sfract *= vatten:sfract.xxxxXXXX;
+ }
+
+ vec32<$v01> vmvp0;
+ vec32<$v03> vmvp1;
+ vec32<$v05> vmvp2;
+ vec32<$v07> vmvp3;
+
+ #ifndef ENABLE_ENV_MAP
+ u16 addr = MATRICES_MVP;
+ load_mat4x4(vmvp0, vmvp1, vmvp2, vmvp3, addr);
+ #endif
+ }
+
+ vlpos *= vnorm:sfract;
+ VTEMP = vlcol:sfract * vlpos:sfract.xxxxXXXX;
+ VTEMP = vlcol:sfract +* vlpos:sfract.yyyyYYYY;
+ vlcol = vlcol:sfract +* vlpos:sfract.zzzzZZZZ;
+ vlcol = max(vlcol, VZERO);
+
+ vrgba:sint += vlcol; // Cast to sint to force saturated addition
+
+ light_ptr += MGFX_LIGHT_SIZE;
+ light_count -= 1;
+ if (light_count != 0) goto LIGHT_LOOP;
+ }
+
+ LIGHT_END:
+ vrgba.w = vnormmask.w;
+ vrgba.W = vnormmask.w;
+ @AttrPatch("COLOR:vnop") vrgba:sfract *= vrgba_in:sfract;
+
+ set_vcc(fog_mask);
+ vrgba = select(vrgba, vfog.zzzzZZZZ);
+ undef vfog;
+
+ #ifdef ENABLE_ENV_MAP
+
+ u16 addr = MATRICES_MVP;
+ load_mat4x4(vmvp0, vmvp1, vmvp2, vmvp3, addr);
+
+ { // Environment mapping
+ vec32 vsqdist = vpos * vpos;
+ vec32 vtmp = vsqdist + vsqdist.yyyyYYYY;
+ vsqdist = vtmp + vsqdist.zzzzZZZZ;
+
+ vtmp.x = invert_half_sqrt(vsqdist).x;
+ vtmp.X = invert_half_sqrt(vsqdist).X;
+
+ vpos *= vtmp.xxxxXXXX;
+ undef vtmp;
+
+ vec16 vdot = vpos:sfract * vnorm:sfract;
+ vec16 vtmp:sint = vdot + vdot.yyyyYYYY;
+ vdot:sint = vtmp + vdot.zzzzZZZZ;
+ undef vtmp;
+ vdot = min(vdot, VZERO);
+ vdot = VZERO - vdot;
+
+ vec16 vzunit = VZERO;
+ vzunit.z = vnormmask.w;
+ vzunit.Z = vnormmask.w;
+
+ VTEMP = vnorm:sfract * vdot.xxxxXXXX;
+ VTEMP = vnorm:sfract +* vdot.xxxxXXXX;
+ VTEMP = vpos:sint +* 1;
+ VTEMP = vzunit:sint +* 1;
+ undef vzunit;
+ undef vdot;
+ vec32 vrefl = get_acc();
+
+ vsqdist = vrefl * vrefl;
+ vec32 vtmp = vsqdist + vsqdist.yyyyYYYY;
+ vsqdist = vtmp + vsqdist.zzzzZZZZ;
+
+ vtmp.x = invert_half_sqrt(vsqdist).x;
+ vtmp.X = invert_half_sqrt(vsqdist).X;
+
+ vtex = 0x80;
+ VTEMP = vtex:sint * 1;
+ vtex:sint = vrefl +* vtmp.xxxxXXXX;
+ }
+
+ vec32 vtexscale;
+ vec16 vtexoffset;
+ vtexscale = load(MG_SCRATCH_MEM, SCRATCH_TEXSCALE);
+ vtexoffset.xy = load(TEXTURING_OFFSET).xy;
+ vtexoffset.XY = load(TEXTURING_OFFSET).xy;
+ #endif
+
+ // Texture coords
+ VTEMP = vtexoffset:sint * 1;
+ vtex:sint = vtex:sint +* vtexscale;
+
+ prev_out0 = vtx_out0_ptr;
+ prev_out1 = vtx_out1_ptr;
+ vtx_in0_ptr += vtx_size2;
+ vtx_in1_ptr += vtx_size2;
+ vtx_out0_ptr += MG_VTX_SIZE2;
+ vtx_out1_ptr += MG_VTX_SIZE2;
+
+ @AttrLoader("NORMAL") vnorm.x = load(vtx_in0_ptr).x;
+ @AttrLoader("NORMAL") vnorm.X = load(vtx_in1_ptr).x;
+ @AttrLoader("POSITION") vpos.xyzw = load(vtx_in0_ptr).xyzw;
+ @AttrLoader("POSITION") vpos.XYZW = load(vtx_in1_ptr).xyzw;
+ @AttrLoader("COLOR") vrgba_in.xy = load(vtx_in0_ptr).xy;
+ @AttrLoader("COLOR") vrgba_in.zw = load(vtx_in1_ptr).xy;
+
+ if (vtx_in0_ptr < vtx_in_end) goto VERTEX_LOOP;
+
+ @Barrier("st_c0") @Barrier("st_tr0") store(vpos_clip:sint.xyzw, prev_out0, MG_VTX_XYZ);
+ @Barrier("st_c1") @Barrier("st_tr1") store(vpos_clip:sint.XYZW, prev_out1, MG_VTX_XYZ);
+
+ @Barrier("st_c0") store(c0, prev_out0, MG_VTX_CLIP_CODE);
+ @Barrier("st_c1") store(c1, prev_out1, MG_VTX_CLIP_CODE);
+ @Barrier("st_tr0") store(tr0, prev_out0, MG_VTX_TR_CODE);
+ @Barrier("st_tr1") store(tr1, prev_out1, MG_VTX_TR_CODE);
+
+ @Barrier("st_tex0") store_vec_u8(vrgba.x, prev_out0, MG_VTX_RGBA);
+ @Barrier("st_tex1") store_vec_u8(vrgba.X, prev_out1, MG_VTX_RGBA);
+
+ @Barrier("st_tex0") store(vtex.xy, prev_out0, MG_VTX_ST);
+ @Barrier("st_tex1") store(vtex.XY, prev_out1, MG_VTX_ST);
+ }
+}
diff --git a/src/tests/examples/t3d/rsp_tiny3d.S b/src/tests/examples/t3d/rsp_tiny3d.S
index d111e86..3f81015 100644
--- a/src/tests/examples/t3d/rsp_tiny3d.S
+++ b/src/tests/examples/t3d/rsp_tiny3d.S
@@ -407,8 +407,8 @@ VertexFX_Spherical:
j RSPQ_Loop ## L:583 | 34 | if(prt3d == ptr3dEnd)goto RSPQ_Loop;
nop ## L:583 | *36 | if(prt3d == ptr3dEnd)goto RSPQ_Loop;
VertexFX_CelShadeColor:
- vge $v04, $v04, $v04 ## L:629 | ^ | color = max(color, color.yyyyYYYY);
- vge $v04, $v04, $v04 ## L:630 | ***5 | color = max(color, color.zzzzZZZZ);
+ vge $v04, $v04, $v04.h1 ## L:629 | ^ | color = max(color, color.yyyyYYYY);
+ vge $v04, $v04, $v04.h2 ## L:630 | ***5 | color = max(color, color.zzzzZZZZ);
vmov $v03.e0, $v04.e0 ## L:631 | ***9 | uv.x = color.x;
vmov $v03.e2, $v04.e4 ## L:632 | 10 | uv.z = color.X;
slv $v03, 4, 12, $s5 ## L:635 | ***14 | store(uv.zw, ptrBuffB, 0x0C);
diff --git a/src/tests/magma/attributes.test.js b/src/tests/magma/attributes.test.js
new file mode 100644
index 0000000..0fc473e
--- /dev/null
+++ b/src/tests/magma/attributes.test.js
@@ -0,0 +1,443 @@
+import {transpileSource} from "../../lib/transpiler";
+
+const CONF = {magma: true};
+
+const getAttributes = asm => {
+ const idxBegin = asm.indexOf("MgBeginVertexInput");
+ const endKeyword = "MgEndVertexInput";
+ const idxEnd = asm.indexOf(endKeyword);
+ return asm.substring(idxBegin, idxEnd + endKeyword.length);
+}
+
+const getAttributesAndShader = asm => {
+ const idxBegin = asm.indexOf("MgBeginVertexInput");
+ const endKeyword = "MgEndShader";
+ const idxEnd = asm.indexOf(endKeyword, idxBegin);
+ return asm.substring(idxBegin, idxEnd + endKeyword.length);
+}
+
+describe('Attributes', () =>
+{
+ test('Unused attribute', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0;
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 0
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ });
+
+ test('Scalar loader', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0;
+
+ shader testshader()
+ {
+ u32<$t0> vtx;
+ u32<$t1> attr0;
+ @AttrLoader("ATTRIBUTE0") attr0 = load(vtx);
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 0
+ MgVertexAttributeLoaders LOAD_ATTRIBUTE08
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("LOAD_ATTRIBUTE08: lw $t1, 0($t0)");
+ });
+
+ test('Vector loader', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> vec16 ATTRIBUTE0;
+
+ shader testshader()
+ {
+ u32<$t0> vtx;
+ vec16<$v01> attr0;
+ @AttrLoader("ATTRIBUTE0") attr0 = load(vtx);
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 0
+ MgVertexAttributeLoaders LOAD_ATTRIBUTE08
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("LOAD_ATTRIBUTE08: lqv $v01, 0, 0, $t0");
+ });
+
+ test('Loader on declaration', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0;
+
+ shader testshader()
+ {
+ u32<$t0> vtx;
+ @AttrLoader("ATTRIBUTE0") u32<$t1> attr0 = load(vtx);
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 0
+ MgVertexAttributeLoaders LOAD_ATTRIBUTE07
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("LOAD_ATTRIBUTE07: lw $t1, 0($t0)");
+ });
+
+ test('Multiple loaders', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0;
+
+ shader testshader()
+ {
+ u32<$t0> vtx;
+ @AttrLoader("ATTRIBUTE0") u32<$t1> attr0 = load(vtx);
+ vec16<$v01> attr1;
+ @AttrLoader("ATTRIBUTE0") attr1.xy = load(vtx).xy;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 0
+ MgVertexAttributeLoaders LOAD_ATTRIBUTE07, LOAD_ATTRIBUTE09
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("LOAD_ATTRIBUTE07: lw $t1, 0($t0)");
+ expect(asm).toContain("LOAD_ATTRIBUTE09: llv $v01, 0, 0, $t0");
+ });
+
+ test('Loader (non-string value)', async () => {
+ const src = `
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ u32<$t0> vtx;
+ vec16<$v01> attr0;
+ @AttrLoader(10) attr0 = load(vtx);
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/line 8: Annotation 'AttrLoader' expects a string value!/);
+ });
+
+ test('Loader (empty string value)', async () => {
+ const src = `
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ u32<$t0> vtx;
+ vec16<$v01> attr0;
+ @AttrLoader("") attr0 = load(vtx);
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/line 8: Annotation 'AttrLoader' expects a non-empty string value!/);
+ });
+
+ test('Optional attribute', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 1
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ });
+
+ test('Patch', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ u32<$t0> a;
+ @AttrPatch("ATTRIBUTE0:nop") a = 1;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 1
+ MgBeginVertexAttributePatch PATCH_ATTRIBUTE07
+ nop
+ MgEndVertexAttributePatch
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("PATCH_ATTRIBUTE07: addiu $t0, $zero, 1");
+ });
+
+ test('Patch (missing replacement)', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ u32<$t0> a;
+ @AttrPatch("ATTRIBUTE0:") a = 1;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 1
+ MgBeginVertexAttributePatch PATCH_ATTRIBUTE07
+ nop
+ MgEndVertexAttributePatch
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("PATCH_ATTRIBUTE07: addiu $t0, $zero, 1");
+ });
+
+ test('Patch (missing colon)', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ u32<$t0> a;
+ @AttrPatch("ATTRIBUTE0") a = 1;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 1
+ MgBeginVertexAttributePatch PATCH_ATTRIBUTE07
+ nop
+ MgEndVertexAttributePatch
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("PATCH_ATTRIBUTE07: addiu $t0, $zero, 1");
+ });
+
+ test('Patch (non-string value)', async () => {
+ const src = `
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ u32<$t0> a;
+ @AttrPatch(1) a = 1;
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/line 7: Annotation 'AttrPatch' expects a string value!/);
+ });
+
+ test('Patch (empty string value)', async () => {
+ const src = `
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ u32<$t0> a;
+ @AttrPatch("") a = 1;
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/line 7: Annotation 'AttrPatch' expects a non-empty string value!/);
+ });
+
+ test('Multiple patches', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0?;
+
+ shader testshader()
+ {
+ u32<$t0> a, b;
+ @AttrPatch("ATTRIBUTE0:nop") a = 1;
+ @AttrPatch("ATTRIBUTE0:addiu $t1, $zero, $zero") b = a + a;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 1
+ MgBeginVertexAttributePatch PATCH_ATTRIBUTE07
+ nop
+ MgEndVertexAttributePatch
+ MgBeginVertexAttributePatch PATCH_ATTRIBUTE08
+ addiu $t1, $zero, $zero
+ MgEndVertexAttributePatch
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("PATCH_ATTRIBUTE07: addiu $t0, $zero, 1");
+ expect(asm).toContain("PATCH_ATTRIBUTE08: addu $t1, $t0, $t0");
+ });
+
+ test('Multiple attributes', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<0> u32 ATTRIBUTE0;
+ attribute<1> u32 ATTRIBUTE1?;
+ attribute<2> u32 ATTRIBUTE2?;
+
+ shader testshader()
+ {
+ u32<$s0> vtx;
+ u32<$t0> a, b, c;
+ @AttrLoader("ATTRIBUTE0") a = load(vtx);
+ @AttrLoader("ATTRIBUTE1") b = load(vtx);
+ @AttrLoader("ATTRIBUTE2") c = load(vtx);
+ @AttrPatch("ATTRIBUTE1:nop") a = 1;
+ @AttrLoader("ATTRIBUTE1") b = load(vtx);
+ @AttrLoader("ATTRIBUTE2") c = load(vtx);
+ @AttrPatch("ATTRIBUTE1:nop") b = a + a;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 0, 0
+ MgVertexAttributeLoaders LOAD_ATTRIBUTE010
+ MgEndVertexAttribute
+
+ MgBeginVertexAttribute 1, 1
+ MgVertexAttributeLoaders LOAD_ATTRIBUTE111, LOAD_ATTRIBUTE114
+ MgBeginVertexAttributePatch PATCH_ATTRIBUTE113
+ nop
+ MgEndVertexAttributePatch
+ MgBeginVertexAttributePatch PATCH_ATTRIBUTE116
+ nop
+ MgEndVertexAttributePatch
+ MgEndVertexAttribute
+
+ MgBeginVertexAttribute 2, 1
+ MgVertexAttributeLoaders LOAD_ATTRIBUTE212, LOAD_ATTRIBUTE215
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ expect(asm).toContain("LOAD_ATTRIBUTE010: lw $t0, 0($s0)");
+ expect(asm).toContain("LOAD_ATTRIBUTE111: lw $t1, 0($s0)");
+ expect(asm).toContain("LOAD_ATTRIBUTE114: lw $t1, 0($s0)");
+ expect(asm).toContain("LOAD_ATTRIBUTE212: lw $t2, 0($s0)");
+ expect(asm).toContain("LOAD_ATTRIBUTE215: lw $t2, 0($s0)");
+ expect(asm).toContain("PATCH_ATTRIBUTE113: addiu $t0, $zero, 1");
+ expect(asm).toContain("PATCH_ATTRIBUTE116: addu $t1, $t0, $t0");
+ });
+
+ test('Arbitrary input numbers', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<65> u32 ATTRIBUTE0;
+ attribute<6> u32 ATTRIBUTE1;
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 65, 0
+ MgEndVertexAttribute
+
+ MgBeginVertexAttribute 6, 0
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ });
+
+ test('Omitted input numbers', async () => {
+ const {asm, warn} = await transpileSource(`
+ attribute<1> u32 ATTRIBUTE0;
+ attribute u32 ATTRIBUTE1;
+ attribute u32 ATTRIBUTE2;
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+ MgBeginVertexAttribute 1, 0
+ MgEndVertexAttribute
+
+ MgBeginVertexAttribute 2, 0
+ MgEndVertexAttribute
+
+ MgBeginVertexAttribute 3, 0
+ MgEndVertexAttribute
+
+MgEndVertexInput`);
+ });
+
+ test('Negative input number', async () => {
+ const src = `
+ attribute<-2> u32 ATTRIBUTE0;
+
+ shader testshader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Attribute input number must be in \[0, 2\^32\)!/);
+ });
+
+ test('Negative input number', async () => {
+ const src = `
+ attribute<69347592054634> u32 ATTRIBUTE0;
+
+ shader testshader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Attribute input number must be in \[0, 2\^32\)!/);
+ });
+
+ test('Invalid input number', async () => {
+ const src = `
+ attribute u32 ATTRIBUTE0;
+
+ shader testshader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Syntax error at line 2/);
+ });
+
+ test('No attributes', async () => {
+ const {asm, warn} = await transpileSource(`
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getAttributes(asm)).toBe(
+`MgBeginVertexInput
+MgEndVertexInput`);
+ });
+
+ test('Attribute in non-magma mode', async () => {
+ const src = `
+ attribute<0> u32 ATTRIBUTE0;
+ `;
+ await expect(() => transpileSource(src, {rspqWrapper: false}))
+ .rejects.toThrowError(/Attributes are only allowed when compiling for magma \(pass '--magma' on the command line\)!/);
+ });
+});
diff --git a/src/tests/magma/magma.test.js b/src/tests/magma/magma.test.js
new file mode 100644
index 0000000..54a38a0
--- /dev/null
+++ b/src/tests/magma/magma.test.js
@@ -0,0 +1,130 @@
+import {transpileSource} from "../../lib/transpiler";
+
+const CONF = {magma: true};
+
+describe('Magma mode', () =>
+{
+ test('No RSPQ-Header in magma mode', async () => {
+ const {asm, warn} = await transpileSource(`
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(asm).not.toContain("RSPQ_BeginOverlayHeader");
+ expect(asm).not.toContain("RSPQ_EndOverlayHeader");
+ });
+
+ test('No saved state in magma mode', async () => {
+ const {asm, warn} = await transpileSource(`
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(asm).not.toContain("RSPQ_BeginSavedState");
+ expect(asm).not.toContain("RSPQ_EndSavedState");
+ expect(asm).not.toContain("RSPQ_EmptySavedState");
+ });
+
+ test('No sections in magma mode', async () => {
+ const {asm, warn} = await transpileSource(`
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(asm).not.toContain(".data");
+ expect(asm).not.toContain(".text");
+ });
+
+ test('Extern state', async () => {
+ const {asm, warn} = await transpileSource(`
+ state
+ {
+ extern u32 VALUE;
+ }
+
+ shader testshader()
+ {
+ u32<$t0> value = load(VALUE);
+ }`, CONF);
+
+ expect(asm).toContain("lw $t0, %lo(VALUE + 0)");
+ expect(asm).not.toContain("VALUE: .ds.b 4");
+ });
+
+ test('Extern data', async () => {
+ const {asm, warn} = await transpileSource(`
+ data
+ {
+ extern u32 VALUE;
+ }
+
+ shader testshader()
+ {
+ u32<$t0> value = load(VALUE);
+ }`, CONF);
+
+ expect(asm).toContain("lw $t0, %lo(VALUE + 0)");
+ expect(asm).not.toContain("VALUE: .ds.b 4");
+ });
+
+ test('Extern bss', async () => {
+ const {asm, warn} = await transpileSource(`
+ bss
+ {
+ extern u32 VALUE;
+ }
+
+ shader testshader()
+ {
+ u32<$t0> value = load(VALUE);
+ }`, CONF);
+
+ expect(asm).toContain("lw $t0, %lo(VALUE + 0)");
+ expect(asm).not.toContain("VALUE: .ds.b 4");
+ });
+
+ test('Non-extern state in magma mode', async () => {
+ const src = `
+ state
+ {
+ u32 VALUE;
+ }
+
+ shader testshader()
+ {
+ u32<$t0> value = load(VALUE);
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Only extern states are allowed when compiling for magma!/);
+ });
+
+ test('Non-extern data in magma mode', async () => {
+ const src = `
+ data
+ {
+ u32 VALUE;
+ }
+
+ shader testshader()
+ {
+ u32<$t0> value = load(VALUE);
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Only extern states are allowed when compiling for magma!/);
+ });
+
+ test('Non-extern bss in magma mode', async () => {
+ const src = `
+ bss
+ {
+ u32 VALUE;
+ }
+
+ shader testshader()
+ {
+ u32<$t0> value = load(VALUE);
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Only extern states are allowed when compiling for magma!/);
+ });
+});
\ No newline at end of file
diff --git a/src/tests/magma/shader.test.js b/src/tests/magma/shader.test.js
new file mode 100644
index 0000000..17bc0e5
--- /dev/null
+++ b/src/tests/magma/shader.test.js
@@ -0,0 +1,142 @@
+import {transpileSource} from "../../lib/transpiler";
+
+const CONF = {magma: true};
+
+const getShader = asm => {
+ const idxEndUniforms = asm.indexOf("MgEndShaderUniform");
+ const idxBegin = asm.indexOf("MgBeginShader", idxEndUniforms);
+ const endKeyword = "MgEndShader";
+ const idxEnd = asm.indexOf(endKeyword, idxBegin);
+ return asm.substring(idxBegin, idxEnd + endKeyword.length);
+}
+
+describe('Shaders', () =>
+{
+ test('Empty shader', async () => {
+ const {asm, warn} = await transpileSource(`
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getShader(asm)).toBe(
+`MgBeginShader
+ j RSPQ_Loop
+ nop
+
+MgEndShader`);
+ });
+
+ test('Simple shader', async () => {
+ const {asm, warn} = await transpileSource(`
+ shader testshader()
+ {
+ u32<$a0> ptr;
+ u32<$t0> value = 0x100;
+ store(value, ptr);
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getShader(asm)).toBe(
+`MgBeginShader
+ addiu $t0, $zero, 256
+ sw $t0, ($a0)
+ j RSPQ_Loop
+ nop
+
+MgEndShader`);
+ });
+
+ test('Function before shader', async () => {
+ const {asm, warn} = await transpileSource(`
+ function test_function(u32<$s0> ptr)
+ {
+ u32<$t0> value = 1;
+ store(value, ptr);
+ }
+
+ shader testshader()
+ {
+ u32<$s0> ptr = 0x100;
+ test_function(ptr);
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getShader(asm)).toBe(
+`MgBeginShader
+ addiu $s0, $zero, 256
+ jal test_function
+ nop
+ j RSPQ_Loop
+ nop
+test_function:
+ addiu $t0, $zero, 1
+ sw $t0, ($s0)
+ jr $ra
+ nop
+
+MgEndShader`);
+ });
+
+ test('Arguments in shader', async () => {
+ const src = `
+ shader test_shader(u32 arg)
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Shaders must not specify arguments!/);
+ });
+
+ test('Result type in shader', async () => {
+ const src = `
+ shader<$t0> test_shader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Shaders must not specify a result-type \(use 'shader' without `< >`\)!/);
+ });
+
+ test('Missing shader', async () => {
+ const src = `
+ function test_missing_shader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Exactly one shader must be defined when compiling for magma \(use 'shader'\)!/);
+ });
+
+ test('Multiple shaders', async () => {
+ const src = `
+ shader test_shader1()
+ {
+ }
+
+ shader test_shader2()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/A shader has already been defined!/);
+ });
+
+ test('Command in magma mode', async () => {
+ const src = `
+ command<0> test_command()
+ {
+ }
+
+ shader testshader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Commands must not be defined when compiling for magma \(define a 'shader' instead\)!/);
+ });
+
+ test('Shader in non-magma mode', async () => {
+ const src = `
+ shader testshader()
+ {
+ }`;
+ await expect(() => transpileSource(src, {rspqWrapper: false}))
+ .rejects.toThrowError(/Shaders are only allowed when compiling for magma \(pass '--magma' on the command line\)!/);
+ });
+});
diff --git a/src/tests/magma/uniforms.test.js b/src/tests/magma/uniforms.test.js
new file mode 100644
index 0000000..cd25164
--- /dev/null
+++ b/src/tests/magma/uniforms.test.js
@@ -0,0 +1,275 @@
+import {transpileSource} from "../../lib/transpiler";
+
+const CONF = {magma: true};
+
+const getUniforms = asm => {
+ const idxBegin = asm.indexOf("MgBeginShaderUniforms");
+ const endKeyword = "MgEndShaderUniforms";
+ const idxEnd = asm.indexOf(endKeyword);
+ return asm.substring(idxBegin, idxEnd + endKeyword.length);
+}
+
+describe('Uniforms', () =>
+{
+ test('Single uniform', async () => {
+ const {asm, warn} = await transpileSource(`
+ uniform<0> UNIFORM0
+ {
+ u32 VALUE0;
+ }
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getUniforms(asm)).toBe(
+`MgBeginShaderUniforms
+ MgBeginUniform UNIFORM0, 0
+ .align 2
+ VALUE0: .ds.b 4
+ MgEndUniform
+
+MgEndShaderUniforms`);
+ });
+
+ test('Multiple values', async () => {
+ const {asm, warn} = await transpileSource(`
+ uniform<0> UNIFORM0
+ {
+ vec16 POSITIONS[2];
+ u32 VALUE0;
+ u32 VALUES[4];
+ }
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getUniforms(asm)).toBe(
+`MgBeginShaderUniforms
+ MgBeginUniform UNIFORM0, 0
+ .align 4
+ POSITIONS: .ds.b 32
+ .align 2
+ VALUE0: .ds.b 4
+ .align 2
+ VALUES: .ds.b 16
+ MgEndUniform
+
+MgEndShaderUniforms`);
+ });
+
+ test('Multiple uniforms', async () => {
+ const {asm, warn} = await transpileSource(`
+ uniform<0> UNIFORM0
+ {
+ u32 VALUE0;
+ }
+
+ uniform<1> UNIFORM1
+ {
+ s16 POSITION[3];
+ }
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getUniforms(asm)).toBe(
+`MgBeginShaderUniforms
+ MgBeginUniform UNIFORM0, 0
+ .align 2
+ VALUE0: .ds.b 4
+ MgEndUniform
+
+ MgBeginUniform UNIFORM1, 1
+ .align 1
+ POSITION: .ds.b 6
+ MgEndUniform
+
+MgEndShaderUniforms`);
+ });
+
+ test('Arbitrary binding numbers', async () => {
+ const {asm, warn} = await transpileSource(`
+ uniform<748> UNIFORM0
+ {
+ u32 VALUE0;
+ }
+
+ uniform<34> UNIFORM1
+ {
+ u32 VALUE1;
+ }
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getUniforms(asm)).toBe(
+`MgBeginShaderUniforms
+ MgBeginUniform UNIFORM0, 748
+ .align 2
+ VALUE0: .ds.b 4
+ MgEndUniform
+
+ MgBeginUniform UNIFORM1, 34
+ .align 2
+ VALUE1: .ds.b 4
+ MgEndUniform
+
+MgEndShaderUniforms`);
+ });
+
+ test('Omitted binding numbers', async () => {
+ const {asm, warn} = await transpileSource(`
+ uniform<1> UNIFORM0
+ {
+ u32 VALUE0;
+ }
+
+ uniform UNIFORM1
+ {
+ u32 VALUE1;
+ }
+
+ uniform UNIFORM2
+ {
+ u32 VALUE2;
+ }
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getUniforms(asm)).toBe(
+`MgBeginShaderUniforms
+ MgBeginUniform UNIFORM0, 1
+ .align 2
+ VALUE0: .ds.b 4
+ MgEndUniform
+
+ MgBeginUniform UNIFORM1, 2
+ .align 2
+ VALUE1: .ds.b 4
+ MgEndUniform
+
+ MgBeginUniform UNIFORM2, 3
+ .align 2
+ VALUE2: .ds.b 4
+ MgEndUniform
+
+MgEndShaderUniforms`);
+ });
+
+ test('Negative binding number', async () => {
+ const src = `
+ uniform<-3> UNIFORM0
+ {
+ u32 VALUE0;
+ }
+
+ shader testshader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Uniform binding number must be in \[0, 2\^32\)!/);
+ });
+
+ test('Very large binding number', async () => {
+ const src = `
+ uniform<69347592054634> UNIFORM0
+ {
+ u32 VALUE0;
+ }
+
+ shader testshader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Uniform binding number must be in \[0, 2\^32\)!/);
+ });
+
+ test('Invalid binding number', async () => {
+ const src = `
+ uniform UNIFORM0
+ {
+ u32 VALUE0;
+ }
+
+ shader testshader()
+ {
+ }`;
+ await expect(() => transpileSource(src, CONF))
+ .rejects.toThrowError(/Syntax error at line 2/);
+ });
+
+ test('Empty uniform', async () => {
+ const {asm, warn} = await transpileSource(`
+ uniform<0> UNIFORM0
+ {
+ }
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getUniforms(asm)).toBe(
+`MgBeginShaderUniforms
+ MgBeginUniform UNIFORM0, 0
+ MgEndUniform
+
+MgEndShaderUniforms`);
+ });
+
+ test('Extern value', async () => {
+ const {asm, warn} = await transpileSource(`
+ uniform<0> UNIFORM0
+ {
+ u32 VALUE0;
+ extern u32 VALUE1;
+ }
+
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getUniforms(asm)).toBe(
+`MgBeginShaderUniforms
+ MgBeginUniform UNIFORM0, 0
+ .align 2
+ VALUE0: .ds.b 4
+ MgEndUniform
+
+MgEndShaderUniforms`);
+ });
+
+ test('No uniforms', async () => {
+ const {asm, warn} = await transpileSource(`
+ shader testshader()
+ {
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(getUniforms(asm)).toBe(
+`MgBeginShaderUniforms
+MgEndShaderUniforms`);
+ });
+
+ test('Uniform in non-magma mode', async () => {
+ const src = `
+ uniform<0> UNIFORM0
+ {
+ u32 VALUE0;
+ }`;
+ await expect(() => transpileSource(src, {rspqWrapper: false}))
+ .rejects.toThrowError(/Uniforms are only allowed when compiling for magma \(pass '--magma' on the command line\)!/);
+ });
+});
diff --git a/src/tests/preproc/defineAsm.test.js b/src/tests/preproc/defineAsm.test.js
index 284e0d7..613ca50 100644
--- a/src/tests/preproc/defineAsm.test.js
+++ b/src/tests/preproc/defineAsm.test.js
@@ -30,4 +30,30 @@ describe('Define (ASM)', () =>
expect(asm).toContain("#define SOME_DEF_C 3\n");
expect(asm).toContain("#define SOME_DEF_D 4\n");
});
+
+ test ('Define in ASM without value', async () => {
+ const {asm, warn} = await transpileSource(`
+ include "rsp_queue.inc"
+ include "rdpq_macros.h"
+
+ #define SOME_DEF_A
+ #define SOME_DEF_B
+
+ state{}
+
+ #define SOME_DEF_C
+
+ command<0> test(u32 a)
+ {
+ }
+
+ #define SOME_DEF_D
+ `, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toContain("#define SOME_DEF_A\n");
+ expect(asm).toContain("#define SOME_DEF_B\n");
+ expect(asm).toContain("#define SOME_DEF_C\n");
+ expect(asm).toContain("#define SOME_DEF_D\n");
+ });
});
diff --git a/src/tests/vectorOps.test.js b/src/tests/vectorOps.test.js
index 9e68582..1ebfe25 100644
--- a/src/tests/vectorOps.test.js
+++ b/src/tests/vectorOps.test.js
@@ -293,6 +293,36 @@ describe('Vector - Ops', () =>
nop`);
});
+ test('Mul (vec32 vs vec32:ufract)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec32<$v01> res, a;
+ res *= a:ufract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmudl $v02, $v02, $v04.e0
+ vmadm $v01, $v01, $v04.e0
+ vmadn $v02, $v00, $v00
+ jr $ra
+ nop`);
+ });
+
+ test('Mul (vec16 vs vec32:ufract)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec32<$v01> res, a;
+ vec16<$v05> b;
+ res = b * a:ufract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmudm $v01, $v05, $v04.e0
+ vmadn $v02, $v00, $v00
+ jr $ra
+ nop`);
+ });
+
test('Mul (vec16 vs vec16)', async () => {
const {asm, warn} = await transpileSource(`function test() {
vec16<$v01> res, a;
@@ -306,6 +336,53 @@ describe('Vector - Ops', () =>
nop`);
});
+ test('Mul (vec16 vs vec16 -> vec32)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec32<$v01> res;
+ vec16<$v03> a, b;
+ res = a * b.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmudh $v02, $v03, $v04.e0
+ vsar $v01, COP2_ACC_HI
+ vsar $v02, COP2_ACC_MD
+ jr $ra
+ nop`);
+ });
+
+ test('Mul (vec16 vs vec32)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec32<$v01> res, a;
+ vec16<$v05> b;
+ res = b * a.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmudm $v02, $v05, $v04.e0
+ vmadh $v01, $v05, $v03.e0
+ vmadn $v02, $v00, $v00
+ jr $ra
+ nop`);
+ });
+
+ test('Mul (vec16 vs vec32 -> vec16)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec16<$v01> res, a;
+ vec32<$v03> b;
+ res = a * b.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmudm $v29, $v02, $v04.e0
+ vmadh $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
test('Mul (vec16 cast)', async () => {
const {asm, warn} = await transpileSource(`function test() {
vec16<$v01> res, a;
@@ -325,6 +402,244 @@ describe('Vector - Ops', () =>
nop`);
});
+ test('Mul (vec16:sint vs vec16:sint)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:sint * b:sint.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmudh $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Mul (vec16:ufract vs vec16:ufract)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:ufract * b:ufract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmulu $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Mul (vec16:sfract vs vec16:sfract)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:sfract * b:sfract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmulf $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Mul (vec16:sint vs vec16:ufract)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:sint * b:ufract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmudm $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Mul (vec16:ufract vs vec16:sint)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:ufract * b:sint.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmudn $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec32 vs vec32)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec32<$v01> res, a;
+ res = res +* a.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadl $v29, $v02, $v04.e0
+ vmadm $v29, $v01, $v04.e0
+ vmadn $v02, $v02, $v03.e0
+ vmadh $v01, $v01, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec32 vs vec32:ufract)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec32<$v01> res, a;
+ res = res +* a:ufract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadl $v02, $v02, $v04.e0
+ vmadm $v01, $v01, $v04.e0
+ vmadn $v02, $v00, $v00
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16 vs vec32:ufract)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec32<$v01> res, a;
+ vec16<$v05> b;
+ res = b +* a:ufract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadm $v01, $v05, $v04.e0
+ vmadn $v02, $v00, $v00
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec32 vs vec32, cast sfract)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec32<$v01> res, a;
+ res:sfract = res +* a.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadl $v29, $v02, $v04.e0
+ vmadm $v29, $v01, $v04.e0
+ vmadn $v02, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16 vs vec16 -> vec32)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec32<$v01> res;
+ vec16<$v03> a, b;
+ res = a +* b.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadh $v02, $v03, $v04.e0
+ vsar $v01, COP2_ACC_HI
+ vsar $v02, COP2_ACC_MD
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16 vs vec32)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec32<$v01> res, a;
+ vec16<$v05> b;
+ res = b +* a.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadm $v02, $v05, $v04.e0
+ vmadh $v01, $v05, $v03.e0
+ vmadn $v02, $v00, $v00
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16 vs vec32 -> vec16)', async () => {
+ const { asm, warn } = await transpileSource(`function test() {
+ vec16<$v01> res, a;
+ vec32<$v03> b;
+ res = a +* b.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadm $v29, $v02, $v04.e0
+ vmadh $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16:sint vs vec16:sint)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:sint +* b:sint.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadh $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16:ufract vs vec16:ufract)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:ufract +* b:ufract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmacu $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16:sfract vs vec16:sfract)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:sfract +* b:sfract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmacf $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16:sint vs vec16:ufract)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:sint +* b:ufract.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadm $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
+ test('Add-Mul (vec16:ufract vs vec16:sint)', async () => {
+ const {asm, warn} = await transpileSource(`function test() {
+ vec16<$v01> res, a, b;
+ res = a:ufract +* b:sint.x;
+ }`, CONF);
+
+ expect(warn).toBe("");
+ expect(asm).toBe(`test:
+ vmadn $v01, $v02, $v03.e0
+ jr $ra
+ nop`);
+ });
+
test('AND (vec16)', async () => {
const {asm, warn} = await transpileSource(`function test() {
vec16<$v02> res16, a16;
diff --git a/src/web/index.html b/src/web/index.html
index 46b48e1..0eb44cd 100644
--- a/src/web/index.html
+++ b/src/web/index.html
@@ -36,6 +36,7 @@
+
diff --git a/src/web/js/editorMode/rspl_highlight_rules.js b/src/web/js/editorMode/rspl_highlight_rules.js
index ed6b687..4714a61 100644
--- a/src/web/js/editorMode/rspl_highlight_rules.js
+++ b/src/web/js/editorMode/rspl_highlight_rules.js
@@ -30,6 +30,7 @@ const rsplHighlightRules = function() {
const keywords =
[
"state", "temp_state", "function", "command", "macro",
+ "shader", "uniform", "attribute",
"if", "else", "for", "goto", "break", "continue",
"include", "extern", "while",
"const", "undef", "exit", "loop",
@@ -48,7 +49,7 @@ const rsplHighlightRules = function() {
].join("|");
const buildinConstants = [
- "ZERO", "VZERO", "Relative", "Barrier", "Align", "NoReturn"
+ "ZERO", "VZERO", "Relative", "Barrier", "Align", "NoReturn", "AttrLoader", "AttrPatch"
].join("|")
const variables = [
diff --git a/src/web/js/main.js b/src/web/js/main.js
index 6f91129..a629813 100644
--- a/src/web/js/main.js
+++ b/src/web/js/main.js
@@ -25,6 +25,7 @@ let config = {
optimize: true,
rspqWrapper: true,
reorder: false,
+ magma: true,
patchFunctions: [],
};
@@ -128,6 +129,11 @@ optionReorder.onchange = async () => {
await update(true);
};
+optionMagma.onchange = async () => {
+ config.magma = optionMagma.checked;
+ await update(true);
+};
+
update().catch(console.error);
editor.getSession().on('change', debounce(update, 150));