webgpu-app/index2.js

360 lines
11 KiB
JavaScript

import { getPath } from "./common.js";
import { parseGltfChunks, vertexBufferLayouts, primitiveAccessors, splitGltf } from "./gltf.js";
function loadGltfNode(module, handle, gltf, nodeIndex)
{
const node = gltf.json.nodes[nodeIndex];
var translation = [0, 0, 0];
if ("translation" in node) {
translation = node.translation;
}
console.log("init", nodeIndex, translation);
module.instance.exports.node_init_translation(handle, nodeIndex, translation[0], translation[1], translation[2]);
if ("children" in node) {
for (let childIndex in node.children) {
loadGltfNode(module, handle, gltf, childIndex);
}
}
}
function loadGltfNodes(module, gltf)
{
const nodesHandle = module.instance.exports.node_alloc(gltf.json.nodes.length);
for (let nodeIndex of gltf.json.scenes[0].nodes) {
loadGltfNode(module, nodesHandle, gltf, nodeIndex);
}
return nodesHandle;
}
function printMatrix(m, o)
{
console.log(m[o+0], m[o+1], m[o+2], m[o+3]);
console.log(m[o+4], m[o+5], m[o+6], m[o+7]);
console.log(m[o+8], m[o+9], m[o+10], m[o+11]);
console.log(m[o+12], m[o+13], m[o+14], m[o+15]);
}
class GltfRenderer {
constructor(device, canvasFormat, viewUniformBuffer, shaderModule, gltf, textures)
{
this.gltf = gltf;
//////////////////////////////////////////////////////////////////////
// textures
//////////////////////////////////////////////////////////////////////
this.sampler = device.createSampler();
this.textures = textures;
//////////////////////////////////////////////////////////////////////
// buffer
//////////////////////////////////////////////////////////////////////
this.buffers = [];
//console.assert(gltf.json.buffers.length === gltf.bin.length);
for (let i = 0; i < gltf.bin.length; i++) {
//console.assert(gltf.json.buffers[i].byteLength == gltf.bin[i].length);
const buffer = device.createBuffer({
label: `gltf vertex buffer {i}`,
size: gltf.bin[i].length,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(buffer, 0,
gltf.bin[i].buffer, gltf.bin[i].offset, gltf.bin[i].length);
this.buffers.push(buffer);
}
//////////////////////////////////////////////////////////////////////
// pipeline
//////////////////////////////////////////////////////////////////////
const bindGroupLayouts = [
device.createBindGroupLayout({
label: "gltf bind group layout 0",
entries: [{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "uniform" }
}, {
binding: 1,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "read-only-storage" }
}, {
binding: 2,
visibility: GPUShaderStage.FRAGMENT,
sampler: {}
}]
}),
device.createBindGroupLayout({
label: "gltf bind group layout 1",
entries: [{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
texture: {},
}, {
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
texture: {},
}]
}),
];
const pipelineLayout = device.createPipelineLayout({
label: "gltf pipeline layout",
bindGroupLayouts: bindGroupLayouts,
});
// by vertex buffer layout
const renderPipelineKeys = {};
this.renderPipelines = {};
this.meshes = [];
for (let i = 0; i < gltf.json.meshes.length; i++) {
const mesh = gltf.json.meshes[i];
const meshPrimitives = [];
for (let j = 0; j < mesh.primitives.length; j++) {
const accessors = primitiveAccessors(mesh.primitives[j]);
const key = accessors.join(";");
meshPrimitives.push([key, accessors]);
renderPipelineKeys[key] = accessors;
}
this.meshes.push(meshPrimitives);
}
for (let [key, accessors] of Object.entries(renderPipelineKeys)) {
const bufferLayouts = vertexBufferLayouts(gltf, accessors);
const renderPipeline = device.createRenderPipeline({
label: "gltf pipeline",
layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: "vertexMain",
buffers: bufferLayouts,
},
fragment: {
module: shaderModule,
entryPoint: "fragmentMain",
targets: [{
format: canvasFormat,
}]
},
primitive: {
topology: 'triangle-list',
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: 'less',
format: 'depth24plus',
},
});
this.renderPipelines[key] = renderPipeline;
}
//////////////////////////////////////////////////////////////////////
// bind group
//////////////////////////////////////////////////////////////////////
const matrixSize = 4 * 4 * 4;
this.nodeBufferSize = matrixSize * this.gltf.json.nodes.length;
this.nodeBuffer = device.createBuffer({
label: "gltf node buffer",
size: this.nodeBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
this.bindGroups = [
device.createBindGroup({
label: "gltf bind group 0",
layout: bindGroupLayouts[0],
entries: [{
binding: 0,
resource: { buffer: viewUniformBuffer },
}, {
binding: 1,
resource: { buffer: this.nodeBuffer },
}, {
binding: 2,
resource: this.sampler,
}]
}),
];
this.materialBindGroups = [];
for (let i = 0; i < this.gltf.json.materials.length; i++) {
const material = this.gltf.json.materials[i];
const bindGroup = device.createBindGroup({
label: `gltf bind group material ${i}`,
layout: bindGroupLayouts[1],
entries: [{
binding: 0,
resource: this.textures[material.pbrMetallicRoughness.baseColorTexture.index],
}, {
binding: 1,
resource: this.textures[material.emissiveTexture.index],
}]
});
this.materialBindGroups.push(bindGroup);
}
}
renderPrimitive(renderPass, accessors, primitive, nodeIndex)
{
for (let i = 0; i < accessors.length; i++) {
const accessor = accessors[i];
const bufferViewIndex = this.gltf.json.accessors[accessor].bufferView;
const bufferView = this.gltf.json.bufferViews[bufferViewIndex];
renderPass.setVertexBuffer(i, this.buffers[bufferView.buffer], bufferView.byteOffset, bufferView.byteLength);
}
const indicesAccessor = this.gltf.json.accessors[primitive.indices];
const indicesBufferView = this.gltf.json.bufferViews[indicesAccessor.bufferView];
renderPass.setIndexBuffer(this.buffers[indicesBufferView.buffer],
"uint16",
indicesBufferView.byteOffset,
indicesBufferView.byteLength);
renderPass.setBindGroup(0, this.bindGroups[0]);
renderPass.setBindGroup(1, this.materialBindGroups[primitive.material]);
//console.log("drawIndexed", nodeIndex);
renderPass.drawIndexed(indicesAccessor.count, 1, 0, 0, nodeIndex);
//renderPass.drawIndexed(indicesAccessor.count);
}
renderNode(renderPass, lastKey, nodeIndex)
{
const node = this.gltf.json.nodes[nodeIndex];
if ("mesh" in node) {
const mesh = this.gltf.json.meshes[node.mesh];
for (let j = 0; j < mesh.primitives.length; j++) {
const [key, accessors] = this.meshes[node.mesh][j];
if (key !== lastKey)
renderPass.setPipeline(this.renderPipelines[key]);
lastKey = key;
const primitive = mesh.primitives[j];
this.renderPrimitive(renderPass, accessors, primitive, nodeIndex);
}
}
if ("children" in node) {
for (let childIndex of node.children) {
lastKey = this.renderNode(renderPass, lastKey, childIndex);
}
}
return lastKey;
}
render(renderPass)
{
var lastKey = undefined;
for (let nodeIndex of this.gltf.json.scenes[0].nodes) {
lastKey = this.renderNode(renderPass, lastKey, nodeIndex);
}
}
}
function memoryString(memory, offset)
{
const u8 = new Uint8Array(memory.buffer, offset);
const s = [];
var i = 0;
while (u8[i] != 0 && (i - offset) < 256) {
s.push(u8[i]);
i++;
}
return new TextDecoder().decode(new Uint8Array(s));
}
async function loadTextures(device, memory, module, gltf)
{
const tempTable = module.instance.exports.mem_alloc(256 * 4);
const tempHeader = module.instance.exports.mem_alloc(7 * 4);
const tempInputSize = 8 * 1024 * 1024;
const tempInput = module.instance.exports.mem_alloc(tempInputSize);
const tempOutputSize = 2048 * 2048 * 4;
const tempOutput = module.instance.exports.mem_alloc(tempOutputSize);
const tempError = module.instance.exports.mem_alloc(4);
module.instance.exports.png_build_crc_table(tempTable);
const tempInputU8 = new Uint8Array(memory.buffer, tempInput);
const tempOutputU8 = new Uint8Array(memory.buffer, tempOutput);
const tempErrorU32 = new Uint32Array(memory.buffer, tempError);
const tempHeaderU32 = new Uint32Array(memory.buffer, tempHeader);
const textures = [];
for (let image of gltf.json.images) {
console.log(image.uri);
const imageResponse = await fetch(`gltf/${image.uri}`);
const imageBuffer = await imageResponse.arrayBuffer();
const imageBufferU8 = new Uint8Array(imageBuffer)
tempInputU8.set(imageBufferU8);
const ret = module.instance.exports.png_decode(tempTable, tempInput, imageBuffer.byteLength,
tempHeader,
tempOutput, tempOutputSize,
tempError);
if (ret < 0) {
console.log("png ret", ret, memoryString(memory, tempErrorU32[0]));
throw new Error("png error");
}
const width = tempHeaderU32[0];
const height = tempHeaderU32[1];
const texture = device.createTexture({
size: [width, height],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
});
device.queue.writeTexture({ texture: texture },
tempOutputU8,
{ bytesPerRow: width * 4 },
{ width: width, height: height });
textures.push(texture);
}
return textures;
}
async function loadGltf(device, canvasFormat, viewUniformBuffer, memory, module)
{
const code = await getPath("gltf.wgsl");
const shaderModule = device.createShaderModule({
label: "gltf module",
code: await code,
});
const gltf = await splitGltf("gltf/lamp.gltf", "gltf/lamp.bin");
const nodesHandle = loadGltfNodes(module, gltf);
/*
const nodesM = new Float32Array(memory.buffer, nodesHandle);
for (let i = 0; i < gltf.json.nodes.length; i++) {
console.log("[", i, "]");
printMatrix(nodesM, i * 16);
}
*/
const textures = await loadTextures(device, memory, module, gltf);
const renderer = new GltfRenderer(device, canvasFormat, viewUniformBuffer, shaderModule, gltf, textures);
console.log(renderer.nodeBuffer);
device.queue.writeBuffer(renderer.nodeBuffer, 0,
memory.buffer, nodesHandle, (4 * 4 * 4) * gltf.json.nodes.length);
return renderer;
}
export { loadGltf };