load and draw texture

This commit is contained in:
Zack Buhman 2026-07-25 21:41:44 -05:00
parent c60666e7a1
commit 96636d1cb1
17 changed files with 392 additions and 120 deletions

View File

@ -5,6 +5,8 @@ struct Configuration {
@group(0) @binding(0) var<uniform> config: Configuration;
@group(0) @binding(1) var<storage> nodeMatrix: array<mat4x4f>;
@group(0) @binding(2) var linearSampler: sampler;
@group(0) @binding(3) var colorTexture: texture_2d<f32>;
/*
struct Imm {
@ -24,11 +26,13 @@ struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) positionWorld: vec3f,
@location(1) normal: vec3f,
@location(2) texture: vec2f,
};
struct FragmentInput {
@location(0) positionWorld: vec3f,
@location(1) normal: vec3f,
@location(2) texture: vec2f,
};
@vertex
@ -41,6 +45,7 @@ fn vertexMain(input: VertexInput) -> VertexOutput
output.position = config.matrix * position;
output.positionWorld = (config.matrixWorld * position).xyz;
output.normal = (config.matrixWorld * normal).xyz;
output.texture = input.texture;
return output;
}
@ -88,5 +93,7 @@ fn lighting(position: vec3f, normal: vec3f) -> vec3f
fn fragmentMain(input: FragmentInput) -> @location(0) vec4f
{
let intensity = lighting(input.positionWorld, normalize(input.normal));
return vec4(intensity, 1.0);
let color = textureSample(colorTexture, linearSampler, input.texture);
return vec4(color.xyz * intensity, 1.0);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 MiB

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -256,12 +256,14 @@ const rotate = await WebAssembly.instantiateStreaming(fetch("rotate.wasm"), {
env: { memory: memory }
});
const memory2 = new WebAssembly.Memory({ initial: 2 });
const mem = await WebAssembly.instantiateStreaming(fetch("memory.wasm"), {
env: { memory: memory2 }
const memory2 = new WebAssembly.Memory({ initial: 258 * 2 });
const module = await WebAssembly.instantiateStreaming(fetch("src/module.wasm"), {
env: {
memory: memory2,
log: console.log,
}
});
console.log("mem");
document.mem = mem;
//document.module = module;
const uniformBuffer = device.createBuffer({
label: "grid uniform",
@ -439,7 +441,7 @@ function render()
}
//requestAnimationFrame(render);
const gltfRenderer = await loadGltf(device, canvasFormat, memory2, mem);
const gltfRenderer = await loadGltf(device, canvasFormat, memory2, module);
function render2()
{

126
index2.js
View File

@ -2,7 +2,7 @@ import { getPath } from "./common.js";
import { parseGltfChunks, vertexBufferLayouts, primitiveAccessors, splitGltf } from "./gltf.js";
function loadGltfNode(mem, handle, gltf, nodeIndex)
function loadGltfNode(module, handle, gltf, nodeIndex)
{
const node = gltf.json.nodes[nodeIndex];
var translation = [0, 0, 0];
@ -10,20 +10,20 @@ function loadGltfNode(mem, handle, gltf, nodeIndex)
translation = node.translation;
}
console.log("init", nodeIndex, translation);
mem.instance.exports.node_init_translation(handle, nodeIndex, translation[0], translation[1], translation[2]);
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(mem, handle, gltf, childIndex);
loadGltfNode(module, handle, gltf, childIndex);
}
}
}
function loadGltfNodes(mem, gltf)
function loadGltfNodes(module, gltf)
{
const nodesHandle = mem.instance.exports.node_alloc(gltf.json.nodes.length);
const nodesHandle = module.instance.exports.node_alloc(gltf.json.nodes.length);
for (let nodeIndex of gltf.json.scenes[0].nodes) {
loadGltfNode(mem, nodesHandle, gltf, nodeIndex);
loadGltfNode(module, nodesHandle, gltf, nodeIndex);
}
return nodesHandle;
}
@ -37,10 +37,17 @@ function printMatrix(m, o)
}
class GltfRenderer {
constructor(device, canvasFormat, shaderModule, gltf)
constructor(device, canvasFormat, shaderModule, gltf, texture)
{
this.gltf = gltf;
//////////////////////////////////////////////////////////////////////
// textures
//////////////////////////////////////////////////////////////////////
this.sampler = device.createSampler();
this.texture = texture;
//////////////////////////////////////////////////////////////////////
// buffer
//////////////////////////////////////////////////////////////////////
@ -75,6 +82,14 @@ class GltfRenderer {
binding: 1,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "read-only-storage" }
}, {
binding: 2,
visibility: GPUShaderStage.FRAGMENT,
sampler: {}
}, {
binding: 3,
visibility: GPUShaderStage.FRAGMENT,
texture: {},
}]
}),
];
@ -162,6 +177,12 @@ class GltfRenderer {
}, {
binding: 1,
resource: { buffer: this.nodeBuffer },
}, {
binding: 2,
resource: this.sampler,
}, {
binding: 3,
resource: this.texture,
}]
}),
];
@ -244,17 +265,102 @@ class GltfRenderer {
}
}
async function loadGltf(device, canvasFormat, memory, mem)
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));
}
function pngComponents(colorType)
{
if (colorType == 2)
return 3;
if (colorType == 6)
return 4;
throw new Error("non-implemented png color type");
}
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 bitDepth = tempHeaderU32[2];
const colorType = tempHeaderU32[3];
const components = pngComponents(colorType);
console.log(components);
const texture = device.createTexture({
size: [width, height],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
});
device.queue.writeTexture({ texture: texture },
tempOutputU8,
{ bytesPerRow: width * components },
{ width: width, height: height });
textures.push(texture);
}
return textures;
}
async function loadGltf(device, canvasFormat, memory, module)
{
const gltf = await splitGltf("gltf/lamp.gltf", "gltf/lamp.bin");
const nodesHandle = loadGltfNodes(mem, gltf);
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 code = await getPath("gltf.wgsl");
const shaderModule = device.createShaderModule({
@ -262,7 +368,7 @@ async function loadGltf(device, canvasFormat, memory, mem)
code: await code,
});
const renderer = new GltfRenderer(device, canvasFormat, shaderModule, gltf, nodesHandle);
const renderer = new GltfRenderer(device, canvasFormat, shaderModule, gltf, textures[2]);
console.log(renderer.nodeBuffer);
device.queue.writeBuffer(renderer.nodeBuffer, 0,
memory.buffer, nodesHandle, (4 * 4 * 4) * gltf.json.nodes.length);

View File

@ -1,71 +0,0 @@
#include <stddef.h>
#include "directxmath/directxmath.h"
//size_t __builtin_wasm_memory_grow(int, size_t);
//size_t __builtin_wasm_memory_size(int);
extern char __heap_base[];
extern char __heap_end[];
static size_t heap_pages = 0;
static size_t alloc_index = 0;
extern "C" {
size_t mem_size();
size_t mem_alloc(size_t length);
size_t node_alloc(int count);
void node_init_translation(size_t handle, int index, float x, float y, float z);
};
static inline size_t memory_heap_base()
{
return (size_t)&__heap_base[0];
}
static inline size_t align(size_t n, size_t m)
{
return (n + (m - 1)) & (~(m - 1));
}
static inline void memory_grow(size_t npages)
{
__builtin_wasm_memory_grow(0, npages);
}
size_t mem_size()
{
return __builtin_wasm_memory_size(0);
}
size_t mem_alloc(size_t length)
{
size_t base = memory_heap_base();
size_t heap_end = (heap_pages << 16);
size_t address = alloc_index;
alloc_index += align(length, 16);
if (heap_end < alloc_index) {
size_t npages = align(alloc_index - heap_end, 65536) >> 16;
memory_grow(npages);
heap_pages += npages;
}
return base + address;
}
size_t node_alloc(int count)
{
return mem_alloc((sizeof (XMFLOAT4X4)) * count);
}
void node_init_translation(size_t handle, int index, float x, float y, float z)
{
XMFLOAT4X4 * nodes = reinterpret_cast<XMFLOAT4X4 *>(handle);
//XMMATRIX XMLoadFloat4x4(&nodes[index]);
XMMATRIX mat = XMMatrixTranslation(x, y, z);
XMStoreFloat4x4(&nodes[index], mat);
}

View File

@ -3,14 +3,16 @@ MINIZ = $(HOME)/miniz
CFLAGS = \
--target=wasm32 \
-g \
-O0 \
-O3 \
-flto \
-nostdlib \
-I$(MINIZ) \
-I.. \
-I. \
-Werror \
-Wfatal-errors
-Wfatal-errors \
-D_XM_NO_INTRINSICS_
# -flto \
MFLAGS = \
-DMINIZ_NO_STDIO \
@ -19,15 +21,18 @@ MFLAGS = \
-DMINIZ_NO_ZLIB_APIS \
-DMINIZ_NO_MALLOC
# -Wl,--lto-O1 \
LDFLAGS = \
-Wl,--no-entry \
-Wl,--export=build_crc_table \
-Wl,--export=decode \
-Wl,--export=mem_size \
-Wl,--export=mem_alloc \
-Wl,--export=node_alloc \
-Wl,--export=node_init_translation \
-Wl,--export=png_build_crc_table \
-Wl,--export=png_decode \
-Wl,--import-undefined \
-Wl,--print-map \
-Wl,--import-memory \
-Wl,-z,stack-size=1048576
-Wl,--lto-O3
%.o: %.c
clang $(CFLAGS) $(MFLAGS) -o $@ -c $<
@ -38,7 +43,9 @@ LDFLAGS = \
PNG_OBJ = \
png.o \
stdlib.o \
$(MINIZ)/miniz_tinfl.o
$(MINIZ)/miniz_tinfl.o \
memory.o \
node.o
png.wasm: $(PNG_OBJ)
module.wasm: $(PNG_OBJ)
clang++ $(CFLAGS) $(LDFLAGS) -o $@ $^

14
src/index.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGPU</title>
<style>
</style>
<script src="index.js" type="module"></script>
</head>
<body>
<canvas width="256" height="256"></canvas>
</body>
</html>

124
src/index.js Normal file
View File

@ -0,0 +1,124 @@
const canvas = document.querySelector("canvas");
canvas.width = 480;
canvas.height = 480;
const context = canvas.getContext("2d");
const imageData = context.createImageData(canvas.width, canvas.height);
const cstride = canvas.width * 4;
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
imageData.data[y * cstride + x * 4 + 0] = x;
imageData.data[y * cstride + x * 4 + 1] = y;
imageData.data[y * cstride + x * 4 + 2] = 0;
imageData.data[y * cstride + x * 4 + 3] = 255;
}
}
context.putImageData(imageData, 0, 0);
const memoryBlocks = 129 + 128 * 10;
const memorySize = memoryBlocks * (64 * 1024);
const memory = new WebAssembly.Memory({ initial: memoryBlocks });
const memoryU8 = new Uint8Array(memory.buffer);
function round(n, m)
{
return (n + (m - 1)) & ~(m - 1);
}
class Allocator {
constructor(size) {
this.size = size;
this.offset = 0;
}
alloc(size) {
const address = this.offset;
this.offset += round(size, 32);
if (this.offset > this.size) {
throw new Error(`allocator overflow: ${this.offset} > ${this.size}`);
}
return address;
}
}
function memoryString(mem, offset)
{
const s = [];
var i = offset;
while (mem[i] != 0 && (i - offset) < 256) {
s.push(mem[i]);
i++;
}
return new TextDecoder().decode(new Uint8Array(s));
}
const m = new Allocator(memorySize);
m.offset = (129 + 64) * 64 * 1024;
const crcTableA = m.alloc(256 * 4);
const png = await WebAssembly.instantiateStreaming(fetch("png.wasm"), {
env: { memory: memory }
});
/*
const test = await WebAssembly.instantiateStreaming(fetch("test.wasm"), {
env: { memory: memory }
});
const res = test.instance.exports.test();
console.log("res", res);
console.log(memoryString(memoryU8, res));
*/
png.instance.exports.build_crc_table(crcTableA);
/*
const crcTable = new Uint32Array(memory.buffer, crcTableA);
for (let i = 0; i < 256; i++) {
console.log(crcTable[i].toString(16).padStart(8, '0'));
}
*/
const pngResponse = await fetch("../gltf/Image_0.png");
const pngBuffer = await pngResponse.arrayBuffer();
console.log(pngBuffer.byteLength);
const pngA = m.alloc(pngBuffer.byteLength);
const pngBufferU8 = new Uint8Array(pngBuffer);
memoryU8.set(pngBufferU8, pngA);
console.log("a0", memoryU8[pngA], pngBufferU8[0]);
const headerA = m.alloc(7 * 4);
const obufSize = canvas.width * canvas.height * 3;
const obufA = m.alloc(obufSize);
console.log("obufA", obufA, obufSize);
const errorA = m.alloc(4);
const ret = png.instance.exports.decode(crcTableA, pngA, pngBuffer.byteLength,
headerA,
obufA, obufSize,
errorA);
if (ret < 0) {
const address = new Uint32Array(memory.buffer, errorA);
console.log("error3", ret, crcTableA, address[0], memoryString(memoryU8, address[0]));
//for (let i = 0; i < 256; i++) {
//console.log(memoryU8[(-ret) + i]);
//}
}
console.log(ret);
if (ret >= 0) {
console.log("ret", ret);
const pstride = canvas.width * 3;
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
imageData.data[y * cstride + x * 4 + 0] = memoryU8[obufA + pstride * y + x * 3 + 0];
imageData.data[y * cstride + x * 4 + 1] = memoryU8[obufA + pstride * y + x * 3 + 1];
imageData.data[y * cstride + x * 4 + 2] = memoryU8[obufA + pstride * y + x * 3 + 2];
imageData.data[y * cstride + x * 4 + 3] = 255;
}
}
context.putImageData(imageData, 0, 0);
}

47
src/memory.cpp Normal file
View File

@ -0,0 +1,47 @@
#include <stddef.h>
#include "memory.h"
extern char __heap_base[];
extern char __heap_end[];
static size_t heap_pages = 0;
static size_t alloc_index = 0;
static inline size_t memory_heap_base()
{
return (size_t)&__heap_end[0];
}
static inline size_t align(size_t n, size_t m)
{
return (n + (m - 1)) & (~(m - 1));
}
static inline void memory_grow(size_t npages)
{
__builtin_wasm_memory_grow(0, npages);
}
size_t mem_size()
{
return __builtin_wasm_memory_size(0);
}
size_t mem_alloc(size_t length)
{
size_t base = memory_heap_base();
size_t heap_end = (heap_pages << 16);
alloc_index = align(alloc_index, 16);
size_t address = alloc_index;
alloc_index += length;
if (heap_end < alloc_index) {
size_t npages = align(alloc_index - heap_end, 65536) >> 16;
memory_grow(npages);
heap_pages += npages;
}
return base + address;
}

6
src/memory.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
extern "C" {
size_t mem_size();
size_t mem_alloc(size_t length);
};

19
src/node.cpp Normal file
View File

@ -0,0 +1,19 @@
#include <stddef.h>
#include "memory.h"
#include "node.h"
#include "directxmath/directxmath.h"
size_t node_alloc(int count)
{
return mem_alloc((sizeof (XMFLOAT4X4)) * count);
}
void node_init_translation(size_t handle, int index, float x, float y, float z)
{
XMFLOAT4X4 * nodes = reinterpret_cast<XMFLOAT4X4 *>(handle);
//XMMATRIX XMLoadFloat4x4(&nodes[index]);
XMMATRIX mat = XMMatrixTranslation(x, y, z);
XMStoreFloat4x4(&nodes[index], mat);
}

6
src/node.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
extern "C" {
size_t node_alloc(int count);
void node_init_translation(size_t handle, int index, float x, float y, float z);
};

View File

@ -9,6 +9,7 @@
#include <assert.h>
#endif
#include "png.h"
#include "miniz_tinfl.h"
struct CT {
@ -33,7 +34,7 @@ struct Header {
int interlace_method;
};
#define IBUF_SIZE ((1024 + 1) * 1024 * 4)
#define IBUF_SIZE ((2048 + 1) * 2048 * 4)
struct png_state {
tinfl_decompressor decomp;
@ -45,9 +46,7 @@ struct png_state {
static png_state _png_state;
extern "C" void build_crc_table(uint32_t * crc_table);
void build_crc_table(uint32_t * crc_table)
void png_build_crc_table(uint32_t * crc_table)
{
for (uint32_t i = 0; i < 256; i++) {
uint32_t c = i;
@ -269,8 +268,8 @@ static int decode_chunk(uint32_t const * crc_table, uint8_t const * mem, int & o
offset += length;
uint32_t e_crc = decode_int32be(mem, offset);
if (e_crc != a_crc) {
state->error = "chunk crc mismatch";
return -5;
state->error = "chunk crc mismatch2";
return -offset;
}
offset += 4;
@ -285,15 +284,10 @@ static int decode_chunk(uint32_t const * crc_table, uint8_t const * mem, int & o
}
}
extern "C" int decode(uint32_t const * crc_table, uint8_t const * mem, int length,
Header * header,
uint8_t * obuf, int osize,
char const ** error);
int decode(uint32_t const * crc_table, uint8_t const * mem, int length,
Header * header,
uint8_t * obuf, int osize,
char const ** error)
int png_decode(uint32_t const * crc_table, uint8_t const * mem, int length,
Header * header,
uint8_t * obuf, int osize,
char const ** error)
{
*error = 0;
tinfl_init(&_png_state.decomp);
@ -376,22 +370,22 @@ int main(int argc, char const ** argv)
void * ibuf = read_file(argv[1], &isize);
uint32_t crc_table[256];
build_crc_table(crc_table);
png_build_crc_table(crc_table);
uint8_t mem[] = {0x49, 0x45, 0x4E, 0x44};
uint32_t crc = calculate_crc(crc_table, mem, 0, 4);
assert(crc == 0xae426082);
int osize = 8 * 1024 * 1024;
int osize = 8 * 2048 * 2048;
void * obuf = malloc(osize);
Header header{};
char const * error;
int ret = decode(crc_table,
reinterpret_cast<uint8_t *>(ibuf), isize,
&header,
reinterpret_cast<uint8_t *>(obuf), osize,
&error);
int ret = png_decode(crc_table,
reinterpret_cast<uint8_t *>(ibuf), isize,
&header,
reinterpret_cast<uint8_t *>(obuf), osize,
&error);
if (ret < 0)
return 1;

11
src/png.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
struct Header;
extern "C" {
void png_build_crc_table(uint32_t * crc_table);
int png_decode(uint32_t const * crc_table, uint8_t const * mem, int length,
Header * header,
uint8_t * obuf, int osize,
char const ** error);
}