Skip to content

Commit 339791c

Browse files
committed
Cross platform texture writing
Now queue has a writeTexture method which uses the information from the texture descriptor so you don't have to provide as much info at runtime. This behaves like newer rendering APIs, so textures cannot be written to in other formats for example with automatic conversions. As a consequence, the texture manager now only accepts rgba8 format images. But it seems like everything was already using that beyond the white default texture which was easy to port over.
1 parent 4276636 commit 339791c

File tree

9 files changed

+158
-30
lines changed

9 files changed

+158
-30
lines changed

packages/arisu-gfx/command_buffer/gl.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,9 @@ function GLCommandBuffer:execute()
143143
vao:setIndexBuffer(command.buffer)
144144
indexType = indexFormatToGL[command.format]
145145
elseif command.type == "writeBuffer" then
146-
local buffer = command.buffer --[[@as gfx.gl.Buffer]]
147-
buffer:setSlice(command.size, command.data, command.offset)
146+
command.buffer:setSlice(command.size, command.data, command.offset)
147+
elseif command.type == "writeTexture" then
148+
command.texture:writeData(command.descriptor, command.data)
148149
elseif command.type == "setBindGroup" then
149150
for _, entry in ipairs(command.bindGroup.entries) do
150151
if entry.type == "buffer" then

packages/arisu-gfx/command_encoder.lua

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
---@class gfx.TextureDataDescriptor
2+
---@field width number
3+
---@field height number
4+
---@field depth number? # For 3D textures
5+
---@field mip number?
6+
---@field layer number?
7+
---@field offset number?
8+
---@field bytesPerRow number?
9+
---@field rowsPerImage number?
10+
111
---@alias gfx.LoadOp
212
--- | { type: "clear", color: gfx.Color }
313
--- | { type: "load" }
@@ -21,6 +31,7 @@
2131
---@field draw fun(self: gfx.CommandEncoder, vertexCount: number, instanceCount: number, firstVertex: number?, firstInstance: number?)
2232
---@field drawIndexed fun(self: gfx.CommandEncoder, indexCount: number, instanceCount: number, firstIndex: number?, baseVertex: number?, firstInstance: number?)
2333
---@field writeBuffer fun(self: gfx.CommandEncoder, buffer: gfx.Buffer, size: number, data: ffi.cdata*, offset: number?)
34+
---@field writeTexture fun(self: gfx.CommandEncoder, texture: gfx.Texture, descriptor: gfx.TextureDataDescriptor, data: ffi.cdata*)
2435
local Encoder = require("arisu-gfx.encoder.gl") --[[@as gfx.CommandEncoder]]
2536

2637
return Encoder

packages/arisu-gfx/command_encoder/gl.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ local GLCommandBuffer = require("arisu-gfx.command_buffer.gl")
1111
---| { type: "draw", vertexCount: number, instanceCount: number, firstVertex: number, firstInstance: number }
1212
---| { type: "drawIndexed", indexCount: number, instanceCount: number, firstIndex: number, baseVertex: number, firstInstance: number }
1313
---| { type: "writeBuffer", buffer: gfx.gl.Buffer, size: number, data: ffi.cdata*, offset: number }
14+
---| { type: "writeTexture", texture: gfx.gl.Texture, descriptor: gfx.TextureDataDescriptor, data: ffi.cdata* }
1415

1516
---@class gfx.gl.Encoder
1617
---@field commands gfx.gl.Command[]
@@ -106,6 +107,18 @@ function GLCommandEncoder:writeBuffer(buffer, size, data, offset)
106107
}
107108
end
108109

110+
---@param texture gfx.gl.Texture
111+
---@param descriptor gfx.TextureDataDescriptor
112+
---@param data ffi.cdata*
113+
function GLCommandEncoder:writeTexture(texture, descriptor, data)
114+
self.commands[#self.commands + 1] = {
115+
type = "writeTexture",
116+
texture = texture,
117+
descriptor = descriptor,
118+
data = data
119+
}
120+
end
121+
109122
function GLCommandEncoder:finish()
110123
return GLCommandBuffer.new(self.commands)
111124
end

packages/arisu-gfx/queue.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---@class gfx.Queue
22
---@field submit fun(self: gfx.Queue, commandBuffer: gfx.CommandBuffer)
33
---@field writeBuffer fun(self: gfx.Queue, buffer: gfx.Buffer, size: number, data: ffi.cdata*, offset: number?)
4+
---@field writeTexture fun(self: gfx.Queue, texture: gfx.Texture, descriptor: gfx.TextureDataDescriptor, data: ffi.cdata*)
45
local Queue = require("arisu-gfx.queue.gl") --[[@as gfx.Queue]]
56

67
return Queue

packages/arisu-gfx/queue/gl.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,14 @@ function GLQueue:writeBuffer(buffer, size, data, offset)
2727
self:submit(cmd:finish())
2828
end
2929

30+
--- Helper method to write data to a texture
31+
---@param texture gfx.gl.Texture
32+
---@param descriptor gfx.TextureDataDescriptor
33+
---@param data ffi.cdata*
34+
function GLQueue:writeTexture(texture, descriptor, data)
35+
local cmd = GLCommandEncoder.new()
36+
cmd:writeTexture(texture, descriptor, data)
37+
self:submit(cmd:finish())
38+
end
39+
3040
return GLQueue

packages/arisu-gfx/texture/gl.lua

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,28 @@ local gfx = require("arisu-gfx")
55
---@field framebuffer number
66
---@field id number? # if nil, it is the backbuffer (default framebuffer)
77
---@field context gfx.gl.Context? # only present if id is nil
8+
---@field private descriptor gfx.TextureDescriptor
89
local GLTexture = {}
910
GLTexture.__index = GLTexture
1011

11-
local formatMap = {
12+
local glInternalFormatMap = {
1213
[gfx.TextureFormat.Rgba8UNorm] = gl.RGBA8,
1314
}
1415

16+
local glFormatMap = {
17+
[gfx.TextureFormat.Rgba8UNorm] = gl.RGBA,
18+
}
19+
20+
local glTypeMap = {
21+
[gfx.TextureFormat.Rgba8UNorm] = gl.UNSIGNED_BYTE,
22+
}
23+
1524
---@param device gfx.gl.Device
1625
---@param descriptor gfx.TextureDescriptor
1726
function GLTexture.new(device, descriptor)
1827
local levels = descriptor.mipLevelCount or 1
1928
local extents = descriptor.extents
20-
local format = assert(formatMap[descriptor.format], "Unsupported texture format")
29+
local format = assert(glInternalFormatMap[descriptor.format], "Unsupported texture format")
2130

2231
local id ---@type number
2332
if extents.dim == "1d" then
@@ -45,7 +54,52 @@ function GLTexture.new(device, descriptor)
4554
error("Unsupported texture extents")
4655
end
4756

48-
return setmetatable({ framebuffer = 0, id = id }, GLTexture)
57+
return setmetatable({ framebuffer = 0, id = id, descriptor = descriptor }, GLTexture)
58+
end
59+
60+
---@param desc gfx.TextureDataDescriptor
61+
---@param data ffi.cdata*
62+
function GLTexture:writeData(desc, data)
63+
local extents = self.descriptor.extents
64+
local mip = desc.mip or 0
65+
local layer = desc.layer or 0
66+
67+
local format = assert(glFormatMap[self.descriptor.format], "Unsupported texture format")
68+
local type = assert(glTypeMap[self.descriptor.format], "Unsupported texture format")
69+
70+
data = data + (desc.offset or 0)
71+
local bytesPerRow = desc.bytesPerRow
72+
local rowsPerImage = desc.rowsPerImage
73+
74+
local width = desc.width or extents.width
75+
local height = desc.height or extents.height
76+
local depth = desc.depth or extents.depth
77+
78+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1)
79+
gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, rowsPerImage or 0)
80+
gl.pixelStorei(gl.UNPACK_ROW_LENGTH, bytesPerRow and (bytesPerRow / 4) or 0)
81+
82+
if extents.dim == "1d" then
83+
if extents.count then
84+
gl.textureSubImage2D(self.id, mip, 0, layer, width, 1, format, type, data)
85+
else
86+
gl.textureSubImage1D(self.id, mip, 0, width, format, type, data)
87+
end
88+
elseif extents.dim == "2d" then
89+
if extents.count then
90+
gl.textureSubImage3D(self.id, mip, 0, 0, layer, width, height, 1, format, type, data)
91+
else
92+
gl.textureSubImage2D(self.id, mip, 0, 0, width, height, format, type, data)
93+
end
94+
elseif extents.dim == "3d" then
95+
gl.textureSubImage3D(self.id, mip, 0, 0, 0, width, height, depth, format, type, data)
96+
else
97+
error("Unsupported texture extents")
98+
end
99+
100+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4)
101+
gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, 0)
102+
gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0)
49103
end
50104

51105
---@param framebuffer number
@@ -61,6 +115,15 @@ function GLTexture.forContextViewport(context)
61115
end
62116

63117
function GLTexture:destroy()
118+
gl.deleteTextures({ self.id })
119+
end
120+
121+
function GLTexture:__tostring()
122+
if self.context then
123+
return "GLBackbuffer(" .. tostring(self.context) .. ")"
124+
end
125+
126+
return "GLTexture(" .. tostring(self.id) .. ")"
64127
end
65128

66129
return GLTexture

packages/arisu-opengl/init.lua

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ local nonCoreFnDefs = {
7575

7676
glCreateTextures = "void(*)(GLenum, GLsizei, GLuint*)",
7777
glTextureStorage1D = "void(*)(GLuint, GLsizei, GLenum, GLsizei)",
78+
glTextureSubImage1D = "void(*)(GLuint, GLsizei, GLint, GLsizei, GLenum, GLenum, const void*)",
7879
glTextureStorage2D = "void(*)(GLuint, GLsizei, GLenum, GLsizei, GLsizei)",
7980
glTextureSubImage2D = "void(*)(GLuint, GLsizei, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const void*)",
8081
glTextureStorage3D = "void(*)(GLuint, GLsizei, GLenum, GLsizei, GLsizei, GLsizei)",
@@ -106,7 +107,7 @@ local nonCoreFnDefs = {
106107
glIsVertexArray = "GLboolean(*)(GLuint)",
107108
glIsBuffer = "GLboolean(*)(GLuint)",
108109

109-
110+
glPixelStorei = "void(*)(GLenum, GLint)",
110111
}
111112

112113
---@type fun(name: string): function
@@ -256,6 +257,10 @@ return {
256257
COLOR_ATTACHMENT0 = 0x8CE0,
257258
FRAMEBUFFER_COMPLETE = 0x8CD5,
258259

260+
UNPACK_ALIGNMENT = 0x0CF5,
261+
UNPACK_ROW_LENGTH = 0x0CF2,
262+
UNPACK_IMAGE_HEIGHT = 0x806E,
263+
259264
--- @param type gl.ShaderType
260265
--- @param src string
261266
--- @return number
@@ -387,6 +392,9 @@ return {
387392
---@type fun(texture: number, levels: number, internalformat: number, width: number)
388393
textureStorage1D = C.glTextureStorage1D,
389394

395+
---@type fun(texture: number, level: number, xoffset: number, width: number, format: number, type: number, pixels: userdata)
396+
textureSubImage1D = C.glTextureSubImage1D,
397+
390398
---@type fun(texture: number, levels: number, internalformat: number, width: number, height: number)
391399
textureStorage2D = C.glTextureStorage2D,
392400

@@ -403,8 +411,12 @@ return {
403411
---@type fun(unit: number, texture: number)
404412
bindTextureUnit = C.glBindTextureUnit,
405413

406-
---@type fun(n: number, textures: userdata)
407-
deleteTextures = C.glDeleteTextures,
414+
---@type fun(textures: number[])
415+
deleteTextures = function(textures)
416+
local n = #textures
417+
local handle = ffi.new("GLuint[?]", n, textures)
418+
C.glDeleteTextures(n, handle)
419+
end,
408420

409421
---@type fun(target: number, index: number, buffer: number)
410422
bindBufferBase = C.glBindBufferBase,
@@ -533,4 +545,7 @@ return {
533545
isBuffer = function(id)
534546
return C.glIsBuffer(id) ~= 0
535547
end,
548+
549+
---@type fun(pname: number, param: number)
550+
pixelStorei = C.glPixelStorei,
536551
}

packages/arisu-util/init.lua

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,39 @@ do
2626
end
2727
end
2828

29+
---@param done table<any, number>
30+
local function stringify(val, depth, done)
31+
if done[val] then
32+
return "<self " .. done[val] .. ">"
33+
end
34+
35+
local ty = type(val)
36+
if ty == "table" then
37+
local out = { "{" }
38+
local indent = string.rep(" ", depth + 1)
39+
40+
done[val] = depth
41+
for k, v in pairs(val) do
42+
local key = stringify(k, depth + 1, done)
43+
local value = stringify(v, depth + 1, done)
44+
45+
out[#out + 1] = indent .. "[" .. key .. "] = " .. value .. ","
46+
end
47+
done[val] = nil
48+
49+
out[#out + 1] = string.sub(indent, 1, -4) .. "}"
50+
return table.concat(out, "\n")
51+
elseif ty == "string" then
52+
return string.format("%q", val)
53+
else
54+
return tostring(val)
55+
end
56+
end
57+
58+
function util.dbg(val)
59+
print(stringify(val, 0, {}))
60+
end
61+
2962
function util.isWindows()
3063
return ffi.os == "Windows"
3164
end

packages/arisu/gl/texture_manager.lua

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function TextureManager.new(device)
5656
textures = {},
5757
}, TextureManager)
5858

59-
this.whiteTexture = this:upload(Image.new(1, 1, 3, ffi.new("uint8_t[?]", 3, { 255, 255, 255 }), ""))
59+
this.whiteTexture = this:upload(Image.new(1, 1, 4, ffi.new("uint8_t[?]", 3, { 255, 255, 255, 255 }), ""))
6060
this.errorTexture = this:upload(Image.new(
6161
2,
6262
2,
@@ -135,31 +135,12 @@ end
135135
function TextureManager:update(texture, image)
136136
assert(self.textures[texture], "Texture does not exist")
137137

138-
local format = ({
139-
[2] = gl.RG,
140-
[3] = gl.RGB,
141-
[4] = gl.RGBA,
142-
})[image.channels]
143-
144-
assert(format, "Unsupported number of channels: " .. tostring(image.channels))
145-
146138
-- Update dimensions
147139
local dims = ffi.new("float[4]", image.width, image.height, 0, 0)
148140
self.device.queue:writeBuffer(self.textureDimsBuffer, 16, dims, texture * 16)
149141

150-
gl.textureSubImage3D(
151-
self.texture.id,
152-
0,
153-
0,
154-
0,
155-
texture,
156-
image.width,
157-
image.height,
158-
1,
159-
format,
160-
gl.UNSIGNED_BYTE,
161-
image.pixels
162-
)
142+
self.device.queue:writeTexture(self.texture, { layer = texture, width = image.width, height = image.height },
143+
image.pixels)
163144
end
164145

165146
---@param image Image

0 commit comments

Comments
 (0)