Skip to content

Commit 83351a3

Browse files
authored
impr: Expand the API to make Three.js example working (#22)
1 parent 4bebc78 commit 83351a3

File tree

8 files changed

+847
-48
lines changed

8 files changed

+847
-48
lines changed

apps/docs/src/examples/glcompat/textured-cube/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export default async function ({ canvas }: ExampleContext) {
3939

4040
// Load texture
4141
const texture = await loadTexture(gl, crateUrl.src);
42-
console.log('Texture loaded:', texture);
4342

4443
const vertexShader = gl.createShader(gl.VERTEX_SHADER) as WebGLShader;
4544
gl.shaderSource(vertexShader, vertexShaderSource);

apps/docs/src/examples/three/webgl_geometry_cube/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ export default function ({ canvas }: ExampleContext) {
1717
texture.colorSpace = THREE.SRGBColorSpace;
1818

1919
const geometry = new THREE.BoxGeometry();
20-
// TODO: Fix texture bug in order to show the crate texture
21-
const material = new THREE.MeshBasicMaterial({ color: 0xff0055 });
20+
const material = new THREE.MeshBasicMaterial({ map: texture });
2221

2322
const cube = new THREE.Mesh(geometry, material);
2423
scene.add(cube);

packages/byegl/src/byegl-context.ts

Lines changed: 193 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import { ByeGLFramebuffer } from './framebuffer.ts';
2222
import { ByeGLProgram, ByeGLShader } from './program.ts';
2323
import { Remapper } from './remap.ts';
2424
import { ByeGLTexture } from './texture.ts';
25+
import {
26+
convertRGBToRGBA,
27+
getTextureFormat,
28+
} from './texture-format-mapping.ts';
2529
import { $internal } from './types.ts';
2630
import { ByeGLUniformLocation, UniformBufferCache } from './uniform.ts';
2731
import type { UniformInfo, WgslGenerator } from './wgsl/wgsl-generator.ts';
@@ -105,6 +109,10 @@ export class ByeGLContext {
105109
[gl.BLEND_DST_RGB, gl.ZERO],
106110
[gl.BLEND_SRC_ALPHA, gl.ONE],
107111
[gl.BLEND_DST_ALPHA, gl.ZERO],
112+
[gl.UNPACK_FLIP_Y_WEBGL, 0],
113+
[gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0],
114+
[gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.BROWSER_DEFAULT_WEBGL],
115+
[gl.UNPACK_ALIGNMENT, 4],
108116
]);
109117

110118
#globalAttributeState: AttributeState = {
@@ -277,7 +285,19 @@ export class ByeGLContext {
277285
if (!texture) {
278286
textureMap.delete(target);
279287
} else {
280-
textureMap.set(target, texture);
288+
if (target === gl.TEXTURE_CUBE_MAP) {
289+
textureMap.set(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, texture);
290+
textureMap.set(gl.TEXTURE_CUBE_MAP_POSITIVE_X, texture);
291+
textureMap.set(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, texture);
292+
textureMap.set(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, texture);
293+
textureMap.set(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, texture);
294+
textureMap.set(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, texture);
295+
textureMap.set(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, texture);
296+
textureMap.set(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, texture);
297+
textureMap.set(target, texture);
298+
} else {
299+
textureMap.set(target, texture);
300+
}
281301
}
282302
}
283303

@@ -1228,8 +1248,7 @@ export class ByeGLContext {
12281248
}
12291249

12301250
pixelStorei(pname: GLenum, param: GLint): void {
1231-
// TODO: Implement
1232-
throw new NotImplementedYetError('gl.pixelStorei');
1251+
this.#parameters.set(pname, param);
12331252
}
12341253

12351254
polygonOffset(factor: GLfloat, units: GLfloat): void {
@@ -1331,54 +1350,80 @@ export class ByeGLContext {
13311350
let width = 0;
13321351
let height = 0;
13331352
let format: number = gl.RGBA;
1334-
// TODO: Not sure what to do with 'internalformat' just yet.
1353+
let type: number = gl.UNSIGNED_BYTE;
13351354

13361355
const textureMap = this.#boundTexturesMap.get(this.#activeTextureUnit);
1356+
13371357
const texture = textureMap?.get(target)?.[$internal];
13381358
if (!texture) {
13391359
// TODO: Generate a WebGL appropriate error
13401360
return;
13411361
}
13421362

13431363
if (rest.length === 6) {
1344-
const [width_, height_, border, format_, type, pixels] = rest;
1364+
const [width_, height_, border, format_, type_, pixels] = rest;
13451365
width = width_;
13461366
height = height_;
13471367
format = format_;
1368+
type = type_;
1369+
1370+
const formatInfo = getTextureFormat(format, type, internalformat);
1371+
texture.setFormatInfo(formatInfo);
13481372

13491373
const size = [width, height] as const;
13501374
texture.size = size;
13511375

13521376
if (pixels) {
1353-
// For now, assume RGBA/UNSIGNED_BYTE format
1354-
// TODO: Handle different format/type combinations
1355-
if (format === gl.RGBA && type === gl.UNSIGNED_BYTE) {
1356-
this.#root.device.queue.writeTexture(
1357-
{ texture: texture.gpuTexture },
1358-
pixels as ArrayBufferView<ArrayBuffer>,
1359-
{ bytesPerRow: width * 4, rowsPerImage: height },
1360-
{ width, height }
1361-
);
1362-
} else {
1363-
throw new NotImplementedYetError(`gl.texImage2D with format=${format}, type=${type}`);
1377+
let dataToUpload = pixels as ArrayBufferView;
1378+
let bytesPerRow = width * formatInfo.bytesPerPixel;
1379+
1380+
if (format === gl.RGB && (type === gl.UNSIGNED_BYTE || type === gl.UNSIGNED_SHORT_5_6_5)) {
1381+
if (type === gl.UNSIGNED_BYTE) {
1382+
const rgbaData = convertRGBToRGBA(pixels, width, height);
1383+
dataToUpload = rgbaData;
1384+
bytesPerRow = width * 4;
1385+
}
13641386
}
1387+
1388+
const flipY = !!this.#parameters.get(gl.UNPACK_FLIP_Y_WEBGL);
1389+
1390+
if (flipY && type === gl.UNSIGNED_BYTE) {
1391+
dataToUpload = this.#flipImageVertically(dataToUpload as Uint8Array, width, height, formatInfo.bytesPerPixel);
1392+
}
1393+
1394+
this.#root.device.queue.writeTexture(
1395+
{ texture: texture.gpuTexture },
1396+
dataToUpload as ArrayBufferView<ArrayBuffer>,
1397+
{ bytesPerRow, rowsPerImage: height },
1398+
{ width, height }
1399+
);
13651400
}
13661401
} else {
1367-
const [_format, type, source] = rest;
1402+
const [_format, _type, source] = rest;
13681403
format = _format;
1404+
type = _type;
1405+
1406+
const formatInfo = getTextureFormat(format, type, internalformat);
1407+
texture.setFormatInfo(formatInfo);
1408+
13691409
if ('width' in source) {
13701410
width = source.width;
13711411
height = source.height;
13721412
} else {
13731413
width = source.displayWidth;
13741414
height = source.displayHeight;
13751415
}
1376-
// TODO: Do something with 'type'
1416+
13771417
const size = [width, height] as const;
13781418
texture.size = size;
1379-
this.#root.device.queue.copyExternalImageToTexture({ source }, {
1380-
texture: texture.gpuTexture,
1381-
}, size);
1419+
1420+
const flipY = !!this.#parameters.get(gl.UNPACK_FLIP_Y_WEBGL);
1421+
1422+
this.#root.device.queue.copyExternalImageToTexture(
1423+
{ source, flipY },
1424+
{ texture: texture.gpuTexture },
1425+
size
1426+
);
13821427
}
13831428

13841429
// TODO: Implement mip-mapping
@@ -1438,19 +1483,100 @@ export class ByeGLContext {
14381483
texture.setParameter(pname, param);
14391484
}
14401485

1441-
texSubImage2D(
1486+
texStorage2D(
14421487
target: GLenum,
1443-
level: GLint,
1444-
xoffset: GLint,
1445-
yoffset: GLint,
1488+
levels: GLsizei,
1489+
internalformat: GLenum,
14461490
width: GLsizei,
14471491
height: GLsizei,
1448-
format: GLenum,
1449-
type: GLenum,
1450-
pixels: ArrayBufferView | null,
14511492
): void {
1452-
// TODO: Implement
1453-
throw new NotImplementedYetError('gl.texSubImage2D');
1493+
const textureMap = this.#boundTexturesMap.get(this.#activeTextureUnit);
1494+
const texture = textureMap?.get(target)?.[$internal];
1495+
if (!texture) {
1496+
return;
1497+
}
1498+
1499+
const formatInfo = getTextureFormat(
1500+
gl.RGBA,
1501+
gl.UNSIGNED_BYTE,
1502+
internalformat,
1503+
);
1504+
texture.setFormatInfo(formatInfo);
1505+
1506+
texture.size = [width, height];
1507+
}
1508+
1509+
// biome-ignore format: Easier to read
1510+
texSubImage2D(target: GLenum, level: GLint, xoffset: GLint, yoffset: GLint, width: GLsizei, height: GLsizei, format: GLenum, type: GLenum, pixels: ArrayBufferView | null): void;
1511+
// biome-ignore format: Easier to read
1512+
texSubImage2D(target: GLenum, level: GLint, xoffset: GLint, yoffset: GLint, format: GLenum, type: GLenum, source: TexImageSource): void;
1513+
// biome-ignore format: Easier to read
1514+
texSubImage2D(target: GLenum, level: GLint, xoffset: GLint, yoffset: GLint, ...rest: [width: GLsizei, height: GLsizei, format: GLenum, type: GLenum, pixels: ArrayBufferView | null] | [format: GLenum, type: GLenum, source: TexImageSource]): void {
1515+
const textureMap = this.#boundTexturesMap.get(this.#activeTextureUnit);
1516+
const texture = textureMap?.get(target)?.[$internal];
1517+
if (!texture) {
1518+
return;
1519+
}
1520+
1521+
if (rest.length === 5) {
1522+
// ArrayBufferView version
1523+
const [width, height, format, type, pixels] = rest;
1524+
1525+
if (!pixels) {
1526+
return;
1527+
}
1528+
1529+
const flipY = !!this.#parameters.get(gl.UNPACK_FLIP_Y_WEBGL);
1530+
let dataToUpload = pixels as ArrayBufferView;
1531+
1532+
if (format === gl.RGB && type === gl.UNSIGNED_BYTE) {
1533+
dataToUpload = convertRGBToRGBA(pixels, width, height);
1534+
}
1535+
1536+
if (flipY && type === gl.UNSIGNED_BYTE) {
1537+
const bytesPerPixel = format === gl.RGBA ? 4 : format === gl.RGB ? 3 : 1;
1538+
dataToUpload = this.#flipImageVertically(dataToUpload as Uint8Array, width, height, bytesPerPixel);
1539+
}
1540+
if ((format === gl.RGBA || format === gl.RGB) && type === gl.UNSIGNED_BYTE) {
1541+
this.#root.device.queue.writeTexture(
1542+
{
1543+
texture: texture.gpuTexture,
1544+
origin: { x: xoffset, y: yoffset, z: 0 },
1545+
mipLevel: level,
1546+
},
1547+
dataToUpload as ArrayBufferView<ArrayBuffer>,
1548+
{
1549+
bytesPerRow: width * 4,
1550+
rowsPerImage: height,
1551+
},
1552+
{ width, height, depthOrArrayLayers: 1 }
1553+
);
1554+
} else {
1555+
throw new NotImplementedYetError(`gl.texSubImage2D with format=${format}, type=${type}`);
1556+
}
1557+
} else {
1558+
const [format, type, source] = rest;
1559+
const flipY = !!this.#parameters.get(gl.UNPACK_FLIP_Y_WEBGL);
1560+
1561+
let width: number, height: number;
1562+
if ('width' in source) {
1563+
width = source.width;
1564+
height = source.height;
1565+
} else {
1566+
width = source.displayWidth;
1567+
height = source.displayHeight;
1568+
}
1569+
1570+
this.#root.device.queue.copyExternalImageToTexture(
1571+
{ source, flipY },
1572+
{
1573+
texture: texture.gpuTexture,
1574+
origin: { x: xoffset, y: yoffset, z: 0 },
1575+
mipLevel: level,
1576+
},
1577+
[width, height]
1578+
);
1579+
}
14541580
}
14551581

14561582
uniform1f(location: ByeGLUniformLocation | null, value: GLfloat) {
@@ -1601,6 +1727,28 @@ export class ByeGLContext {
16011727
}
16021728
}
16031729

1730+
#flipImageVertically(
1731+
data: Uint8Array,
1732+
width: number,
1733+
height: number,
1734+
bytesPerPixel: number,
1735+
): Uint8Array {
1736+
const rowBytes = width * bytesPerPixel;
1737+
const flipped = new Uint8Array(data.length);
1738+
1739+
for (let row = 0; row < height; row++) {
1740+
const sourceRow = height - 1 - row;
1741+
const sourceStart = sourceRow * rowBytes;
1742+
const destStart = row * rowBytes;
1743+
1744+
for (let col = 0; col < rowBytes; col++) {
1745+
flipped[destStart + col] = data[sourceStart + col];
1746+
}
1747+
}
1748+
1749+
return flipped;
1750+
}
1751+
16041752
uniformMatrix2fv(
16051753
location: ByeGLUniformLocation | null,
16061754
transpose: GLboolean,
@@ -1727,8 +1875,21 @@ export class ByeGLContext {
17271875
gl.TEXTURE0 + (this.#uniformBufferCache.getValue(uniform.id) as number);
17281876

17291877
const textureMap = this.#boundTexturesMap.get(textureUnit);
1730-
// TODO: Always getting the TEXTURE_2D binding, but make it depend on the texture type
1731-
const texture = textureMap?.get(gl.TEXTURE_2D);
1878+
const typeToTextureBinding = {
1879+
'texture_2d<f32>': gl.TEXTURE_2D,
1880+
'texture_2d_array<f32>': gl.TEXTURE_2D_ARRAY,
1881+
'texture_cube<f32>': gl.TEXTURE_CUBE_MAP,
1882+
'texture_3d<f32>': gl.TEXTURE_3D,
1883+
'texture_2d<u32>': gl.TEXTURE_2D,
1884+
};
1885+
const textureBinding =
1886+
typeToTextureBinding[
1887+
uniform.type.type as keyof typeof typeToTextureBinding
1888+
];
1889+
if (!textureBinding) {
1890+
throw new Error(`Unsupported texture type: ${uniform.type.type}`);
1891+
}
1892+
const texture = textureMap?.get(textureBinding);
17321893

17331894
if (!texture) {
17341895
throw new Error(`Texture not found for unit ${textureUnit}`);

packages/byegl/src/constants.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,12 @@ export const wgslTypeToEnumCatalog = {
157157
mat2x2f: gl.FLOAT_MAT2,
158158
mat3x3f: gl.FLOAT_MAT3,
159159
mat4x4f: gl.FLOAT_MAT4,
160-
// TODO: Not sure if UNSIGNED_INT, or INT
161-
'texture_1d<f32>': gl.UNSIGNED_INT,
162-
'texture_2d<f32>': gl.UNSIGNED_INT,
163-
'texture_2d_array<f32>': gl.UNSIGNED_INT,
164-
'texture_3d<f32>': gl.UNSIGNED_INT,
165-
'texture_cube<f32>': gl.UNSIGNED_INT,
166-
'texture_2d<u32>': gl.UNSIGNED_INT,
160+
// TODO: Not sure what to do with texture1d
161+
'texture_1d<f32>': gl.SAMPLER_2D,
162+
'texture_2d<f32>': gl.SAMPLER_2D,
163+
'texture_2d_array<f32>': gl.SAMPLER_2D_ARRAY,
164+
'texture_3d<f32>': gl.SAMPLER_3D,
165+
'texture_cube<f32>': gl.SAMPLER_CUBE,
166+
'texture_2d<u32>': gl.SAMPLER_2D,
167167
sampler: gl.UNSIGNED_INT,
168168
};

0 commit comments

Comments
 (0)