|
1 | | -// MaterialX is served through a script tag in the test setup. |
| 1 | +import { test, expect } from '@playwright/test'; |
| 2 | +import fs from 'fs'; |
| 3 | +import path from 'path'; |
| 4 | +import { fileURLToPath } from 'url'; |
2 | 5 |
|
3 | | -function createStandardSurfaceMaterial(mx) |
4 | | -{ |
5 | | - const doc = mx.createDocument(); |
6 | | - const ssName = 'SR_default'; |
7 | | - const ssNode = doc.addChildOfCategory('standard_surface', ssName); |
8 | | - ssNode.setType('surfaceshader'); |
9 | | - const smNode = doc.addChildOfCategory('surfacematerial', 'Default'); |
10 | | - smNode.setType('material'); |
11 | | - const shaderElement = smNode.addInput('surfaceshader'); |
12 | | - shaderElement.setType('surfaceshader'); |
13 | | - shaderElement.setNodeName(ssName); |
14 | | - expect(doc.validate()).to.be.true; |
15 | | - // Release local wrappers |
16 | | - shaderElement.delete(); |
17 | | - smNode.delete(); |
18 | | - ssNode.delete(); |
19 | | - return doc; |
20 | | -} |
| 6 | +const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 7 | +const testRoot = path.resolve(__dirname, '..'); |
| 8 | + |
| 9 | +const MIME_TYPES = { |
| 10 | + '.html': 'text/html', |
| 11 | + '.js': 'application/javascript', |
| 12 | + '.wasm': 'application/wasm', |
| 13 | + '.data': 'application/octet-stream' |
| 14 | +}; |
21 | 15 |
|
22 | | -describe('Generate Shaders', function () |
| 16 | +// |
| 17 | +// Route handler that serves files from the local test build. |
| 18 | +// |
| 19 | +async function routeHandler(route) |
23 | 20 | { |
24 | | - let mx; |
25 | | - const canvas = document.createElement('canvas'); |
26 | | - const gl = canvas.getContext('webgl2'); |
| 21 | + const url = new URL(route.request().url()); |
| 22 | + |
| 23 | + if (url.pathname === '/') |
| 24 | + { |
| 25 | + return route.fulfill({ |
| 26 | + contentType: 'text/html', |
| 27 | + body: '<!DOCTYPE html><html><body></body></html>' |
| 28 | + }); |
| 29 | + } |
27 | 30 |
|
28 | | - this.timeout(60000); |
| 31 | + // |
| 32 | + // The Emscripten file packager may fetch .data relative to the page or |
| 33 | + // relative to the module. Always resolve it to _build/ to handle both cases. |
| 34 | + // |
| 35 | + let filePath; |
| 36 | + if (path.basename(url.pathname) === 'JsMaterialXGenShader.data') |
| 37 | + { |
| 38 | + filePath = path.join(testRoot, '_build', 'JsMaterialXGenShader.data'); |
| 39 | + } |
| 40 | + else |
| 41 | + { |
| 42 | + filePath = path.join(testRoot, url.pathname); |
| 43 | + } |
29 | 44 |
|
30 | | - before(async function () |
| 45 | + filePath = path.resolve(filePath); |
| 46 | + if (!filePath.startsWith(testRoot + path.sep)) |
31 | 47 | { |
32 | | - mx = await MaterialX(); |
| 48 | + return route.fulfill({ status: 403 }); |
| 49 | + } |
| 50 | + |
| 51 | + if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) |
| 52 | + { |
| 53 | + return route.fulfill({ status: 404 }); |
| 54 | + } |
| 55 | + |
| 56 | + const ext = path.extname(filePath); |
| 57 | + return route.fulfill({ |
| 58 | + body: fs.readFileSync(filePath), |
| 59 | + contentType: MIME_TYPES[ext] || 'application/octet-stream' |
33 | 60 | }); |
| 61 | +} |
34 | 62 |
|
35 | | - it('Compile Shaders', () => |
| 63 | +test.describe('Generate Shaders', () => |
| 64 | +{ |
| 65 | + test('Compile Shaders', async ({ page }) => |
36 | 66 | { |
37 | | - const doc = createStandardSurfaceMaterial(mx); |
38 | | - |
39 | | - const generators = [] |
40 | | - if (typeof mx.EsslShaderGenerator != 'undefined') |
41 | | - generators.push(mx.EsslShaderGenerator.create()); |
42 | | - if (typeof mx.GlslShaderGenerator != 'undefined') |
43 | | - generators.push(mx.GlslShaderGenerator.create()); |
44 | | - if (typeof mx.MslShaderGenerator != 'undefined') |
45 | | - generators.push(mx.MslShaderGenerator.create()); |
46 | | - if (typeof mx.OslShaderGenerator != 'undefined') |
47 | | - generators.push(mx.OslShaderGenerator.create()); |
48 | | - if (typeof mx.VkShaderGenerator != 'undefined') |
49 | | - generators.push(mx.VkShaderGenerator.create()); |
50 | | - if (typeof mx.WgslShaderGenerator != 'undefined') |
51 | | - generators.push(mx.WgslShaderGenerator.create()); |
52 | | - if (typeof mx.MdlShaderGenerator != 'undefined') |
53 | | - generators.push(mx.MdlShaderGenerator.create()); |
54 | | - if (typeof mx.SlangShaderGenerator != 'undefined') |
55 | | - generators.push(mx.SlangShaderGenerator.create()); |
56 | | - |
57 | | - const elem = mx.findRenderableElement(doc); |
58 | | - for (let gen of generators) |
| 67 | + page.on('console', (msg) => |
| 68 | + { |
| 69 | + if (msg.type() === 'error') |
| 70 | + { |
| 71 | + console.error(msg.text()); |
| 72 | + } |
| 73 | + else |
| 74 | + { |
| 75 | + console.log(msg.text()); |
| 76 | + } |
| 77 | + }); |
| 78 | + |
| 79 | + await page.route('**/*', routeHandler); |
| 80 | + await page.goto('http://materialx-test/'); |
| 81 | + |
| 82 | + const { error, generators } = await page.evaluate(async () => |
59 | 83 | { |
60 | | - console.log("Generating shader for " + gen.getTarget() + "..."); |
61 | | - |
62 | | - const genContext = new mx.GenContext(gen); |
63 | | - const stdlib = mx.loadStandardLibraries(genContext); |
64 | | - doc.importLibrary(stdlib); |
65 | | - |
66 | | - try |
| 84 | + const { default: MaterialX } = await import('/_build/JsMaterialXGenShader.js'); |
| 85 | + const mx = await MaterialX(); |
| 86 | + |
| 87 | + const doc = mx.createDocument(); |
| 88 | + const ssName = 'SR_default'; |
| 89 | + const ssNode = doc.addChildOfCategory('standard_surface', ssName); |
| 90 | + ssNode.setType('surfaceshader'); |
| 91 | + const smNode = doc.addChildOfCategory('surfacematerial', 'Default'); |
| 92 | + smNode.setType('material'); |
| 93 | + const shaderInput = smNode.addInput('surfaceshader'); |
| 94 | + shaderInput.setType('surfaceshader'); |
| 95 | + shaderInput.setNodeName(ssName); |
| 96 | + |
| 97 | + const valid = doc.validate(); |
| 98 | + shaderInput.delete(); |
| 99 | + smNode.delete(); |
| 100 | + ssNode.delete(); |
| 101 | + if (!valid) return { error: 'Document validation failed', generators: [] }; |
| 102 | + |
| 103 | + const canvas = document.createElement('canvas'); |
| 104 | + const gl = canvas.getContext('webgl2'); |
| 105 | + |
| 106 | + const generatorNames = [ |
| 107 | + 'EsslShaderGenerator', 'GlslShaderGenerator', 'MslShaderGenerator', |
| 108 | + 'OslShaderGenerator', 'VkShaderGenerator', 'WgslShaderGenerator', |
| 109 | + 'MdlShaderGenerator', 'SlangShaderGenerator' |
| 110 | + ]; |
| 111 | + |
| 112 | + const elem = mx.findRenderableElement(doc); |
| 113 | + const generators = []; |
| 114 | + |
| 115 | + for (const name of generatorNames) |
67 | 116 | { |
68 | | - const mxShader = gen.generate(elem.getNamePath(), elem, genContext); |
| 117 | + if (typeof mx[name] === 'undefined') continue; |
| 118 | + const gen = mx[name].create(); |
| 119 | + const target = gen.getTarget(); |
| 120 | + console.log('Generating shader for ' + target + '...'); |
69 | 121 |
|
70 | | - const fShader = mxShader.getSourceCode("pixel"); |
| 122 | + const genContext = new mx.GenContext(gen); |
| 123 | + const stdlib = mx.loadStandardLibraries(genContext); |
| 124 | + doc.importLibrary(stdlib); |
71 | 125 |
|
72 | | - if (gen.getTarget() == 'essl') |
| 126 | + try |
73 | 127 | { |
74 | | - const vShader = mxShader.getSourceCode("vertex"); |
| 128 | + const mxShader = gen.generate(elem.getNamePath(), elem, genContext); |
| 129 | + const fShader = mxShader.getSourceCode('pixel'); |
| 130 | + const errors = []; |
75 | 131 |
|
76 | | - const glVertexShader = gl.createShader(gl.VERTEX_SHADER); |
77 | | - gl.shaderSource(glVertexShader, vShader); |
78 | | - gl.compileShader(glVertexShader); |
79 | | - if (!gl.getShaderParameter(glVertexShader, gl.COMPILE_STATUS)) |
| 132 | + if (target === 'essl') |
80 | 133 | { |
81 | | - console.error("-------- VERTEX SHADER FAILED TO COMPILE: ----------------"); |
82 | | - console.error("--- VERTEX SHADER LOG ---"); |
83 | | - console.error(gl.getShaderInfoLog(glVertexShader)); |
84 | | - console.error("--- VERTEX SHADER START ---"); |
85 | | - console.error(fShader); |
86 | | - console.error("--- VERTEX SHADER END ---"); |
87 | | - } |
88 | | - expect(gl.getShaderParameter(glVertexShader, gl.COMPILE_STATUS)).to.equal(true); |
| 134 | + const vShader = mxShader.getSourceCode('vertex'); |
89 | 135 |
|
90 | | - const glPixelShader = gl.createShader(gl.FRAGMENT_SHADER); |
91 | | - gl.shaderSource(glPixelShader, fShader); |
92 | | - gl.compileShader(glPixelShader); |
93 | | - if (!gl.getShaderParameter(glPixelShader, gl.COMPILE_STATUS)) |
94 | | - { |
95 | | - console.error("-------- PIXEL SHADER FAILED TO COMPILE: ----------------"); |
96 | | - console.error("--- PIXEL SHADER LOG ---"); |
97 | | - console.error(gl.getShaderInfoLog(glPixelShader)); |
98 | | - console.error("--- PIXEL SHADER START ---"); |
99 | | - console.error(fShader); |
100 | | - console.error("--- PIXEL SHADER END ---"); |
| 136 | + const glVS = gl.createShader(gl.VERTEX_SHADER); |
| 137 | + gl.shaderSource(glVS, vShader); |
| 138 | + gl.compileShader(glVS); |
| 139 | + if (!gl.getShaderParameter(glVS, gl.COMPILE_STATUS)) |
| 140 | + { |
| 141 | + errors.push('Vertex shader: ' + gl.getShaderInfoLog(glVS)); |
| 142 | + } |
| 143 | + |
| 144 | + const glFS = gl.createShader(gl.FRAGMENT_SHADER); |
| 145 | + gl.shaderSource(glFS, fShader); |
| 146 | + gl.compileShader(glFS); |
| 147 | + if (!gl.getShaderParameter(glFS, gl.COMPILE_STATUS)) |
| 148 | + { |
| 149 | + errors.push('Fragment shader: ' + gl.getShaderInfoLog(glFS)); |
| 150 | + } |
| 151 | + |
| 152 | + gl.deleteShader(glVS); |
| 153 | + gl.deleteShader(glFS); |
101 | 154 | } |
102 | | - expect(gl.getShaderParameter(glPixelShader, gl.COMPILE_STATUS)).to.equal(true); |
103 | | - // Cleanup GL shaders |
104 | | - gl.deleteShader(glVertexShader); |
105 | | - gl.deleteShader(glPixelShader); |
106 | | - } |
107 | | - // Cleanup shader wrapper |
108 | | - mxShader.delete(); |
109 | | - } |
110 | | - catch (errPtr) |
111 | | - { |
112 | | - console.error("-------- Failed code generation: ----------------"); |
113 | | - if (typeof mx.getExceptionMessage === 'function') |
114 | | - { |
115 | | - console.error(mx.getExceptionMessage(errPtr)); |
| 155 | + |
| 156 | + generators.push({ target, ok: errors.length === 0, errors }); |
| 157 | + mxShader.delete(); |
116 | 158 | } |
117 | | - else |
| 159 | + catch (errPtr) |
118 | 160 | { |
119 | | - console.error(errPtr); |
| 161 | + const msg = typeof mx.getExceptionMessage === 'function' |
| 162 | + ? mx.getExceptionMessage(errPtr) : String(errPtr); |
| 163 | + generators.push({ target, ok: false, errors: [msg] }); |
120 | 164 | } |
| 165 | + |
| 166 | + stdlib.delete(); |
| 167 | + genContext.delete(); |
| 168 | + gen.delete(); |
121 | 169 | } |
122 | | - // Cleanup per-generator wrappers |
123 | | - stdlib.delete(); |
124 | | - genContext.delete(); |
125 | | - gen.delete(); |
| 170 | + |
| 171 | + elem.delete(); |
| 172 | + doc.delete(); |
| 173 | + return { generators }; |
| 174 | + }); |
| 175 | + |
| 176 | + expect(error).toBeUndefined(); |
| 177 | + expect(generators.length).toBeGreaterThan(0); |
| 178 | + for (const { target, ok, errors } of generators) |
| 179 | + { |
| 180 | + expect(ok, `${target} shader generation failed: ${errors.join('; ')}`).toBe(true); |
126 | 181 | } |
127 | | - // Cleanup element and document |
128 | | - elem.delete(); |
129 | | - doc.delete(); |
130 | 182 | }); |
131 | 183 | }); |
0 commit comments