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