Render multiple objects
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
# Simple textured cube
|
||||||
|
# 8 vertices, 6 faces (12 triangles), per-face UVs
|
||||||
|
|
||||||
|
o Cube
|
||||||
|
|
||||||
|
# Positions
|
||||||
|
v -1.0 -1.0 1.0
|
||||||
|
v 1.0 -1.0 1.0
|
||||||
|
v 1.0 1.0 1.0
|
||||||
|
v -1.0 1.0 1.0
|
||||||
|
v -1.0 -1.0 -1.0
|
||||||
|
v 1.0 -1.0 -1.0
|
||||||
|
v 1.0 1.0 -1.0
|
||||||
|
v -1.0 1.0 -1.0
|
||||||
|
|
||||||
|
# Texture coordinates
|
||||||
|
vt 0.0 0.0
|
||||||
|
vt 1.0 0.0
|
||||||
|
vt 1.0 1.0
|
||||||
|
vt 0.0 1.0
|
||||||
|
|
||||||
|
# Normals
|
||||||
|
vn 0.0 0.0 1.0
|
||||||
|
vn 0.0 0.0 -1.0
|
||||||
|
vn 1.0 0.0 0.0
|
||||||
|
vn -1.0 0.0 0.0
|
||||||
|
vn 0.0 1.0 0.0
|
||||||
|
vn 0.0 -1.0 0.0
|
||||||
|
|
||||||
|
# Faces (front, back, right, left, top, bottom)
|
||||||
|
# Each face uses two triangles: (0,1,2) and (0,2,3)
|
||||||
|
|
||||||
|
# Front (+Z)
|
||||||
|
f 1/1/1 2/2/1 3/3/1
|
||||||
|
f 1/1/1 3/3/1 4/4/1
|
||||||
|
|
||||||
|
# Back (-Z)
|
||||||
|
f 6/1/2 5/2/2 8/3/2
|
||||||
|
f 6/1/2 8/3/2 7/4/2
|
||||||
|
|
||||||
|
# Right (+X)
|
||||||
|
f 2/1/3 6/2/3 7/3/3
|
||||||
|
f 2/1/3 7/3/3 3/4/3
|
||||||
|
|
||||||
|
# Left (-X)
|
||||||
|
f 5/1/4 1/2/4 4/3/4
|
||||||
|
f 5/1/4 4/3/4 8/4/4
|
||||||
|
|
||||||
|
# Top (+Y)
|
||||||
|
f 4/1/5 3/2/5 7/3/5
|
||||||
|
f 4/1/5 7/3/5 8/4/5
|
||||||
|
|
||||||
|
# Bottom (-Y)
|
||||||
|
f 5/1/6 6/2/6 2/3/6
|
||||||
|
f 5/1/6 2/3/6 1/4/6
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
import "sindri:base"
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Types
|
||||||
|
|
||||||
|
Model :: struct {
|
||||||
|
vertices: []base.Vertex,
|
||||||
|
indices: []u16,
|
||||||
|
// Some file formats embed their texture
|
||||||
|
// texture: ^Texture2D,
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture :: struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
id: u32,
|
||||||
|
internal_format: u32,
|
||||||
|
data_format: u32,
|
||||||
|
data_type: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Assets :: struct {
|
||||||
|
allocator: runtime.Allocator,
|
||||||
|
model_list: map[string]Model,
|
||||||
|
texture_list: map[string]Texture,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Constructor/destructor
|
||||||
|
|
||||||
|
assets_init :: proc(allocator := context.allocator) -> Assets {
|
||||||
|
return {
|
||||||
|
allocator = allocator,
|
||||||
|
model_list = make(map[string]Model, allocator),
|
||||||
|
texture_list = make(map[string]Texture, allocator),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assets_destroy :: proc(a: ^Assets) {
|
||||||
|
for _, &model in a.model_list {
|
||||||
|
delete(model.vertices, a.allocator)
|
||||||
|
delete(model.indices, a.allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, &texture in a.texture_list {
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(a.model_list)
|
||||||
|
delete(a.texture_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Public functions
|
||||||
|
|
||||||
|
load_model :: proc(a: ^Assets, path: string) -> ^Model {
|
||||||
|
if path in a.model_list do return &a.model_list[path]
|
||||||
|
|
||||||
|
vertices, indices, ok := load_obj_model(path, a.allocator)
|
||||||
|
fmt.assertf(ok, "[assets] failed to read model: %s", path)
|
||||||
|
|
||||||
|
a.model_list[path] = {
|
||||||
|
vertices = vertices,
|
||||||
|
indices = indices,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &a.model_list[path]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batching:
|
||||||
|
// max_vertices 60k
|
||||||
|
// max_elements 60k
|
||||||
|
// max_texture_slots 32, query gpu or can wgpu do a larger buffer?
|
||||||
|
|
||||||
|
// Scene load: loop through all models, add all model sizes together,
|
||||||
|
// then create a vertex_buffer using the max size, storing the index somewhere.
|
||||||
+5
-5
@@ -7,21 +7,21 @@ import "core:strconv"
|
|||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
import "sindri:base"
|
import "sindri:base"
|
||||||
import "sindri:platform"
|
|
||||||
|
|
||||||
// Minimal OBJ loader: reads v, vt, vn and triangulates convex polygon faces.
|
// Minimal OBJ loader: reads v, vt, vn and triangulates convex polygon faces.
|
||||||
// Produces one GPU vertex per face corner, then centers and normalizes the
|
// Produces one GPU vertex per face corner, then centers and normalizes the
|
||||||
// model so it fits roughly inside a [-1, 1] cube at the origin
|
// model so it fits roughly inside a [-1, 1] cube at the origin
|
||||||
load_obj_model :: proc(
|
load_obj_model :: proc(
|
||||||
path: string,
|
path: string,
|
||||||
|
allocator := context.allocator,
|
||||||
) -> (
|
) -> (
|
||||||
vertices: []base.Vertex,
|
vertices: []base.Vertex,
|
||||||
indices: []u16,
|
indices: []u16,
|
||||||
ok: bool,
|
ok: bool,
|
||||||
) {
|
) {
|
||||||
data, err := os.read_entire_file_from_path(path, context.allocator)
|
data, err := os.read_entire_file_from_path(path, allocator)
|
||||||
if err != os.ERROR_NONE {
|
if err != os.ERROR_NONE {
|
||||||
fmt.eprintfln("[core] failed to read model: %s", path)
|
fmt.eprintfln("[obj] failed to read model: %s", path)
|
||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
defer delete(data)
|
defer delete(data)
|
||||||
@@ -141,8 +141,8 @@ load_obj_model :: proc(
|
|||||||
v.pos = (v.pos - center) * scale
|
v.pos = (v.pos - center) * scale
|
||||||
}
|
}
|
||||||
|
|
||||||
vertices = make([]base.Vertex, len(temp_vertices))
|
vertices = make([]base.Vertex, len(temp_vertices), allocator)
|
||||||
indices = make([]u16, len(temp_indices))
|
indices = make([]u16, len(temp_indices), allocator)
|
||||||
copy(vertices, temp_vertices[:])
|
copy(vertices, temp_vertices[:])
|
||||||
copy(indices, temp_indices[:])
|
copy(indices, temp_indices[:])
|
||||||
return vertices, indices, true
|
return vertices, indices, true
|
||||||
|
|||||||
+26
-5
@@ -89,10 +89,31 @@ test :: proc(e: ^base.Key_Press_Event, _: rawptr) -> bool {
|
|||||||
|
|
||||||
// Load model from disk and upload it
|
// Load model from disk and upload it
|
||||||
load_test_model :: proc() {
|
load_test_model :: proc() {
|
||||||
vertices, indices, model_ok := core.load_obj_model("assets/suzanne.obj")
|
assets := core.assets_init()
|
||||||
if !model_ok do fmt.panicf("[main] could not load model")
|
defer core.assets_destroy(&assets)
|
||||||
defer delete(vertices)
|
|
||||||
defer delete(indices)
|
|
||||||
|
|
||||||
platform.fill_buffers_with_model(vertices, indices)
|
cube := core.load_model(&assets, "assets/cube.obj")
|
||||||
|
suzanne := core.load_model(&assets, "assets/suzanne.obj")
|
||||||
|
|
||||||
|
platform.create_material("dummy")
|
||||||
|
platform.create_renderable(
|
||||||
|
"assets/cube.obj",
|
||||||
|
cube.vertices,
|
||||||
|
cube.indices,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
platform.create_renderable(
|
||||||
|
"assets/suzanne.obj",
|
||||||
|
suzanne.vertices,
|
||||||
|
suzanne.indices,
|
||||||
|
false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// per-object:
|
||||||
|
// - vertex buffer
|
||||||
|
// - index buffer
|
||||||
|
// - uniform buffer (model matrix)
|
||||||
|
// - texture
|
||||||
|
// - material
|
||||||
|
// - 1 drawcall
|
||||||
|
|||||||
+348
-81
@@ -14,8 +14,33 @@ import "sindri:base"
|
|||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
// Types
|
// Types
|
||||||
|
|
||||||
|
// Updated once per frame
|
||||||
Camera_Uniform :: struct {
|
Camera_Uniform :: struct {
|
||||||
mvp: linalg.Matrix4f32,
|
mvp: base.Mat4,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updated once per material
|
||||||
|
Material_Uniform :: struct {
|
||||||
|
dummy: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updated once per mesh
|
||||||
|
Renderable_Uniform :: struct {
|
||||||
|
transform: base.Mat4,
|
||||||
|
}
|
||||||
|
|
||||||
|
Material :: struct {
|
||||||
|
bind_group: wgpu.BindGroup,
|
||||||
|
uniform_buffer: wgpu.Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Split Renderable into mesh (vert, idx, #idx) and object (bg, ub)
|
||||||
|
Renderable :: struct {
|
||||||
|
bind_group: wgpu.BindGroup,
|
||||||
|
uniform_buffer: wgpu.Buffer,
|
||||||
|
vertex_buffer: wgpu.Buffer,
|
||||||
|
index_buffer: wgpu.Buffer,
|
||||||
|
index_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
@@ -23,44 +48,52 @@ Camera_Uniform :: struct {
|
|||||||
|
|
||||||
// Instance -> Surface -> Adapter -> Device -> Queue
|
// Instance -> Surface -> Adapter -> Device -> Queue
|
||||||
state: struct {
|
state: struct {
|
||||||
ctx: runtime.Context,
|
ctx: runtime.Context,
|
||||||
os: OS,
|
os: OS,
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
instance: wgpu.Instance, // entry point: creates Adapter, Device and Surface
|
instance: wgpu.Instance, // entry point: creates Adapter, Device and Surface
|
||||||
surface: wgpu.Surface, // handle to a presentable surface (e.g. a window)
|
surface: wgpu.Surface, // handle to a presentable surface (e.g. a window)
|
||||||
adapter: wgpu.Adapter, // handle to physical GPU
|
adapter: wgpu.Adapter, // handle to physical GPU
|
||||||
device: wgpu.Device, // open connection to a GPU
|
device: wgpu.Device, // open connection to a GPU
|
||||||
queue: wgpu.Queue, // handle to command queue on a Device
|
queue: wgpu.Queue, // handle to command queue on a Device
|
||||||
config: wgpu.SurfaceConfiguration,
|
config: wgpu.SurfaceConfiguration,
|
||||||
module: wgpu.ShaderModule,
|
module: wgpu.ShaderModule,
|
||||||
pipeline_layout: wgpu.PipelineLayout,
|
pipeline_layout: wgpu.PipelineLayout,
|
||||||
pipeline: wgpu.RenderPipeline,
|
pipeline: wgpu.RenderPipeline,
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
capabilities: wgpu.SurfaceCapabilities,
|
capabilities: wgpu.SurfaceCapabilities,
|
||||||
vsync: bool,
|
vsync: bool,
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
depth_texture: wgpu.Texture,
|
depth_texture: wgpu.Texture,
|
||||||
depth_texture_view: wgpu.TextureView,
|
depth_texture_view: wgpu.TextureView,
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
uniform_buffer: wgpu.Buffer,
|
camera_uniform_buffer: wgpu.Buffer,
|
||||||
bind_group_layout: wgpu.BindGroupLayout,
|
camera_bind_group_layout: wgpu.BindGroupLayout,
|
||||||
bind_group: wgpu.BindGroup,
|
camera_bind_group: wgpu.BindGroup,
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
vertex_buffer: wgpu.Buffer,
|
material_bind_group_layout: wgpu.BindGroupLayout,
|
||||||
index_buffer: wgpu.Buffer,
|
materials: map[string]Material,
|
||||||
index_count: u32,
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
diffuse_texture: wgpu.Texture,
|
renderable_bind_group_layout: wgpu.BindGroupLayout,
|
||||||
diffuse_texture_view: wgpu.TextureView,
|
renderables: map[string]Renderable,
|
||||||
diffuse_sampler: wgpu.Sampler,
|
// ----------------------------------------
|
||||||
|
diffuse_texture: wgpu.Texture,
|
||||||
|
diffuse_texture_view: wgpu.TextureView,
|
||||||
|
diffuse_sampler: wgpu.Sampler,
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
// Constructor/destructor
|
// Constructor/destructor
|
||||||
|
|
||||||
instance_init :: proc(settings: base.Settings) {
|
instance_init :: proc(
|
||||||
|
settings: base.Settings,
|
||||||
|
allocator := context.allocator,
|
||||||
|
) {
|
||||||
state.ctx = context
|
state.ctx = context
|
||||||
|
|
||||||
|
state.materials = make(map[string]Material, allocator)
|
||||||
|
state.renderables = make(map[string]Renderable, allocator)
|
||||||
|
|
||||||
wgpu.SetLogCallback(log_callback, nil)
|
wgpu.SetLogCallback(log_callback, nil)
|
||||||
wgpu.SetLogLevel(.Warn)
|
wgpu.SetLogLevel(.Warn)
|
||||||
|
|
||||||
@@ -96,12 +129,29 @@ instance_destroy :: proc() {
|
|||||||
wgpu.TextureViewRelease(state.diffuse_texture_view)
|
wgpu.TextureViewRelease(state.diffuse_texture_view)
|
||||||
wgpu.TextureRelease(state.diffuse_texture)
|
wgpu.TextureRelease(state.diffuse_texture)
|
||||||
|
|
||||||
wgpu.BufferRelease(state.uniform_buffer)
|
wgpu.BufferRelease(state.camera_uniform_buffer)
|
||||||
wgpu.BufferRelease(state.vertex_buffer)
|
wgpu.BindGroupRelease(state.camera_bind_group)
|
||||||
wgpu.BufferRelease(state.index_buffer)
|
wgpu.BindGroupLayoutRelease(state.camera_bind_group_layout)
|
||||||
|
|
||||||
wgpu.BindGroupRelease(state.bind_group)
|
if state.materials != nil {
|
||||||
wgpu.BindGroupLayoutRelease(state.bind_group_layout)
|
for _, m in state.materials {
|
||||||
|
wgpu.BufferRelease(m.uniform_buffer)
|
||||||
|
wgpu.BindGroupRelease(m.bind_group)
|
||||||
|
}
|
||||||
|
delete(state.materials)
|
||||||
|
}
|
||||||
|
wgpu.BindGroupLayoutRelease(state.material_bind_group_layout)
|
||||||
|
|
||||||
|
if state.renderables != nil {
|
||||||
|
for _, r in state.renderables {
|
||||||
|
wgpu.BufferRelease(r.uniform_buffer)
|
||||||
|
wgpu.BindGroupRelease(r.bind_group)
|
||||||
|
wgpu.BufferRelease(r.vertex_buffer)
|
||||||
|
wgpu.BufferRelease(r.index_buffer)
|
||||||
|
}
|
||||||
|
delete(state.renderables)
|
||||||
|
}
|
||||||
|
wgpu.BindGroupLayoutRelease(state.renderable_bind_group_layout)
|
||||||
|
|
||||||
wgpu.RenderPipelineRelease(state.pipeline)
|
wgpu.RenderPipelineRelease(state.pipeline)
|
||||||
wgpu.PipelineLayoutRelease(state.pipeline_layout)
|
wgpu.PipelineLayoutRelease(state.pipeline_layout)
|
||||||
@@ -137,7 +187,11 @@ resize :: proc "c" () {
|
|||||||
create_depth_texture()
|
create_depth_texture()
|
||||||
|
|
||||||
// The projection depends on aspect, so rebuild and re-upload the matrix
|
// The projection depends on aspect, so rebuild and re-upload the matrix
|
||||||
upload_camera(state.queue, state.uniform_buffer, framebuffer_aspect())
|
upload_camera(
|
||||||
|
state.queue,
|
||||||
|
state.camera_uniform_buffer,
|
||||||
|
framebuffer_aspect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
frame :: proc "c" () {
|
frame :: proc "c" () {
|
||||||
@@ -194,38 +248,58 @@ frame :: proc "c" () {
|
|||||||
|
|
||||||
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
|
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
|
||||||
|
|
||||||
// The camera's MVP matrix: group 0
|
// Uniform group 0, camera MVP matrix
|
||||||
wgpu.RenderPassEncoderSetBindGroup(
|
wgpu.RenderPassEncoderSetBindGroup(
|
||||||
render_pass_encoder,
|
render_pass_encoder,
|
||||||
0,
|
0,
|
||||||
state.bind_group,
|
state.camera_bind_group,
|
||||||
dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding
|
dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding
|
||||||
)
|
)
|
||||||
|
|
||||||
wgpu.RenderPassEncoderSetVertexBuffer(
|
m := state.materials["dummy"]
|
||||||
|
wgpu.RenderPassEncoderSetBindGroup(
|
||||||
render_pass_encoder,
|
render_pass_encoder,
|
||||||
slot = 0,
|
1,
|
||||||
buffer = state.vertex_buffer,
|
m.bind_group,
|
||||||
offset = 0, // offset into buffer to start from
|
dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding
|
||||||
size = wgpu.WHOLE_SIZE, // x bytes of the buffer to use
|
|
||||||
)
|
)
|
||||||
|
|
||||||
wgpu.RenderPassEncoderSetIndexBuffer(
|
// Render all renderables
|
||||||
render_pass_encoder,
|
for _, r in state.renderables {
|
||||||
buffer = state.index_buffer,
|
// Uniform group 2
|
||||||
format = .Uint16, // type of each element in the buffer
|
wgpu.RenderPassEncoderSetBindGroup(
|
||||||
offset = 0, // offset into buffer to start from
|
render_pass_encoder,
|
||||||
size = wgpu.WHOLE_SIZE, // x bytes of the buffer to use
|
2,
|
||||||
)
|
r.bind_group,
|
||||||
|
dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding
|
||||||
|
)
|
||||||
|
|
||||||
wgpu.RenderPassEncoderDrawIndexed(
|
wgpu.RenderPassEncoderSetVertexBuffer(
|
||||||
render_pass_encoder,
|
render_pass_encoder,
|
||||||
state.index_count,
|
slot = 0,
|
||||||
instanceCount = 1, // draw x instance(s) of the mesh
|
buffer = r.vertex_buffer,
|
||||||
firstIndex = 0, // offset into buffer to start from, useful for drawing sub-mesh
|
offset = 0, // offset into buffer to start from
|
||||||
baseVertex = 0, // value added to index before vertex fetch, useful for multi-mesh vertex buffers
|
size = wgpu.WHOLE_SIZE, // x bytes of the buffer to use
|
||||||
firstInstance = 0, // number to start instance index from
|
)
|
||||||
)
|
|
||||||
|
wgpu.RenderPassEncoderSetIndexBuffer(
|
||||||
|
render_pass_encoder,
|
||||||
|
buffer = r.index_buffer,
|
||||||
|
format = .Uint16, // type of each element in the buffer
|
||||||
|
offset = 0, // offset into buffer to start from
|
||||||
|
size = wgpu.WHOLE_SIZE, // x bytes of the buffer to use
|
||||||
|
)
|
||||||
|
|
||||||
|
wgpu.RenderPassEncoderDrawIndexed(
|
||||||
|
render_pass_encoder,
|
||||||
|
r.index_count,
|
||||||
|
instanceCount = 1, // draw x instance(s) of the mesh
|
||||||
|
firstIndex = 0, // offset into buffer to start from, useful for drawing sub-mesh
|
||||||
|
baseVertex = 0, // value added to index before vertex fetch, useful for multi-mesh vertex buffers
|
||||||
|
firstInstance = 0, // number to start instance index from
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
wgpu.RenderPassEncoderEnd(render_pass_encoder)
|
wgpu.RenderPassEncoderEnd(render_pass_encoder)
|
||||||
wgpu.RenderPassEncoderRelease(render_pass_encoder)
|
wgpu.RenderPassEncoderRelease(render_pass_encoder)
|
||||||
@@ -237,19 +311,148 @@ frame :: proc "c" () {
|
|||||||
wgpu.SurfacePresent(state.surface)
|
wgpu.SurfacePresent(state.surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload arbitrary vertex/index data to GPU buffers
|
create_material :: proc(name: string) {
|
||||||
fill_buffers_with_model :: proc(vertices: []base.Vertex, indices: []u16) {
|
if name in state.materials do return
|
||||||
state.vertex_buffer = wgpu.DeviceCreateBufferWithData(
|
|
||||||
|
uniform_buffer := wgpu.DeviceCreateBuffer(
|
||||||
state.device,
|
state.device,
|
||||||
&{label = "model vertices", usage = {.Vertex}},
|
&{
|
||||||
vertices,
|
label = "material uniform",
|
||||||
|
usage = {.Uniform, .CopyDst}, // shader reads, queue writes
|
||||||
|
size = cast(u64)size_of(Material_Uniform),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
state.index_buffer = wgpu.DeviceCreateBufferWithData(
|
|
||||||
|
uniform := Material_Uniform{}
|
||||||
|
|
||||||
|
// bufferOffset = offset into buffer to start from
|
||||||
|
wgpu.QueueWriteBuffer(
|
||||||
|
state.queue,
|
||||||
|
uniform_buffer,
|
||||||
|
0,
|
||||||
|
&uniform,
|
||||||
|
size_of(Material_Uniform),
|
||||||
|
)
|
||||||
|
|
||||||
|
entries := [?]wgpu.BindGroupEntry {
|
||||||
|
{
|
||||||
|
binding = 0, // slot 0
|
||||||
|
buffer = uniform_buffer,
|
||||||
|
offset = 0, // offset into buffer to start from
|
||||||
|
size = cast(u64)size_of(Material_Uniform),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bind_group := wgpu.DeviceCreateBindGroup(
|
||||||
state.device,
|
state.device,
|
||||||
&{label = "model indices", usage = {.Index}},
|
&{
|
||||||
indices,
|
label = "material bind group",
|
||||||
|
layout = state.material_bind_group_layout,
|
||||||
|
entryCount = len(entries),
|
||||||
|
entries = raw_data(entries[:]),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
state.index_count = cast(u32)len(indices)
|
|
||||||
|
state.materials[name] = {
|
||||||
|
bind_group = bind_group,
|
||||||
|
uniform_buffer = uniform_buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_renderable :: proc(
|
||||||
|
name: string,
|
||||||
|
vertices: []base.Vertex,
|
||||||
|
indices: []u16,
|
||||||
|
first: bool,
|
||||||
|
) {
|
||||||
|
if name in state.renderables do return
|
||||||
|
|
||||||
|
uniform_buffer := wgpu.DeviceCreateBuffer(
|
||||||
|
state.device,
|
||||||
|
&{
|
||||||
|
label = "renderable uniform",
|
||||||
|
usage = {.Uniform, .CopyDst}, // shader reads, queue writes
|
||||||
|
size = cast(u64)size_of(Renderable_Uniform),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
transform: base.Mat4
|
||||||
|
if first {
|
||||||
|
transform = linalg.identity(base.Mat4)
|
||||||
|
} else {
|
||||||
|
transform = linalg.matrix4_translate(base.Vec3{3, 0, 0})
|
||||||
|
}
|
||||||
|
uniform := Renderable_Uniform {
|
||||||
|
transform = transform,
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufferOffset = offset into buffer to start from
|
||||||
|
wgpu.QueueWriteBuffer(
|
||||||
|
state.queue,
|
||||||
|
uniform_buffer,
|
||||||
|
0,
|
||||||
|
&uniform,
|
||||||
|
size_of(Renderable_Uniform),
|
||||||
|
)
|
||||||
|
|
||||||
|
entries := [?]wgpu.BindGroupEntry {
|
||||||
|
{
|
||||||
|
binding = 0, // slot 0
|
||||||
|
buffer = uniform_buffer,
|
||||||
|
offset = 0, // offset into buffer to start from
|
||||||
|
size = cast(u64)size_of(Renderable_Uniform),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bind_group := wgpu.DeviceCreateBindGroup(
|
||||||
|
state.device,
|
||||||
|
&{
|
||||||
|
label = "renderable bind group",
|
||||||
|
layout = state.renderable_bind_group_layout,
|
||||||
|
entryCount = len(entries),
|
||||||
|
entries = raw_data(entries[:]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
vertex_buffer := wgpu.DeviceCreateBuffer(
|
||||||
|
state.device,
|
||||||
|
&{
|
||||||
|
label = "vertex buffer",
|
||||||
|
usage = {.Vertex, .CopyDst}, // shader reads, queue writes
|
||||||
|
size = cast(u64)(len(vertices) * size_of(base.Vertex)),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
wgpu.QueueWriteBuffer(
|
||||||
|
state.queue,
|
||||||
|
vertex_buffer,
|
||||||
|
0,
|
||||||
|
raw_data(vertices),
|
||||||
|
cast(uint)(len(vertices) * size_of(base.Vertex)),
|
||||||
|
)
|
||||||
|
|
||||||
|
index_buffer := wgpu.DeviceCreateBuffer(
|
||||||
|
state.device,
|
||||||
|
&{
|
||||||
|
label = "index buffer",
|
||||||
|
usage = {.Index, .CopyDst}, // shader reads, queue writes
|
||||||
|
size = cast(u64)(len(indices) * size_of(u16)),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
wgpu.QueueWriteBuffer(
|
||||||
|
state.queue,
|
||||||
|
index_buffer,
|
||||||
|
0,
|
||||||
|
raw_data(indices),
|
||||||
|
cast(uint)(len(indices) * size_of(u16)),
|
||||||
|
)
|
||||||
|
|
||||||
|
state.renderables[name] = {
|
||||||
|
bind_group = bind_group,
|
||||||
|
uniform_buffer = uniform_buffer,
|
||||||
|
vertex_buffer = vertex_buffer,
|
||||||
|
index_buffer = index_buffer,
|
||||||
|
index_count = cast(u32)len(indices),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
@@ -307,7 +510,7 @@ on_device :: proc "c" (
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Compile shader
|
// Compile shader
|
||||||
|
|
||||||
uniforms_struct := to_wgsl_struct(Camera_Uniform, "Uniforms")
|
uniforms_struct := to_wgsl_struct(Camera_Uniform, "Camera")
|
||||||
defer delete(uniforms_struct)
|
defer delete(uniforms_struct)
|
||||||
vertex_input_struct := to_wgsl_struct(
|
vertex_input_struct := to_wgsl_struct(
|
||||||
base.Vertex,
|
base.Vertex,
|
||||||
@@ -316,14 +519,26 @@ on_device :: proc "c" (
|
|||||||
)
|
)
|
||||||
defer delete(vertex_input_struct)
|
defer delete(vertex_input_struct)
|
||||||
|
|
||||||
|
material_struct := to_wgsl_struct(Material_Uniform, "Material")
|
||||||
|
defer delete(material_struct)
|
||||||
|
|
||||||
|
renderable_struct := to_wgsl_struct(Renderable_Uniform, "Renderable")
|
||||||
|
defer delete(renderable_struct)
|
||||||
|
|
||||||
shader := fmt.tprintf(
|
shader := fmt.tprintf(
|
||||||
`
|
`
|
||||||
%s
|
%s
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
@group(0) @binding(0) var<uniform> c: Camera;
|
||||||
@group(0) @binding(1) var t_diffuse: texture_2d<f32>;
|
@group(0) @binding(1) var t_diffuse: texture_2d<f32>;
|
||||||
@group(0) @binding(2) var s_diffuse: sampler;
|
@group(0) @binding(2) var s_diffuse: sampler;
|
||||||
|
|
||||||
|
@group(1) @binding(0) var<uniform> m: Material;
|
||||||
|
|
||||||
|
@group(2) @binding(0) var<uniform> r: Renderable;
|
||||||
|
|
||||||
%s
|
%s
|
||||||
|
|
||||||
struct VertexOutput {{
|
struct VertexOutput {{
|
||||||
@@ -335,7 +550,7 @@ on_device :: proc "c" (
|
|||||||
@vertex
|
@vertex
|
||||||
fn vs_main(in: VertexInput) -> VertexOutput {{
|
fn vs_main(in: VertexInput) -> VertexOutput {{
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.position = u.mvp * vec4<f32>(in.pos, 1.0);
|
out.position = c.mvp * r.transform * vec4<f32>(in.pos, 1.0);
|
||||||
out.col = in.col;
|
out.col = in.col;
|
||||||
out.uv = in.uv;
|
out.uv = in.uv;
|
||||||
return out;
|
return out;
|
||||||
@@ -347,6 +562,8 @@ on_device :: proc "c" (
|
|||||||
}}
|
}}
|
||||||
`,
|
`,
|
||||||
uniforms_struct,
|
uniforms_struct,
|
||||||
|
material_struct,
|
||||||
|
renderable_struct,
|
||||||
vertex_input_struct,
|
vertex_input_struct,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -363,7 +580,7 @@ on_device :: proc "c" (
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Camera uniform: buffer, then the baked layout it will be read through
|
// Camera uniform: buffer, then the baked layout it will be read through
|
||||||
|
|
||||||
state.uniform_buffer = wgpu.DeviceCreateBuffer(
|
state.camera_uniform_buffer = wgpu.DeviceCreateBuffer(
|
||||||
state.device,
|
state.device,
|
||||||
&{
|
&{
|
||||||
label = "camera uniform",
|
label = "camera uniform",
|
||||||
@@ -396,7 +613,7 @@ on_device :: proc "c" (
|
|||||||
sampler = {type = .Filtering},
|
sampler = {type = .Filtering},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
state.bind_group_layout = wgpu.DeviceCreateBindGroupLayout(
|
state.camera_bind_group_layout = wgpu.DeviceCreateBindGroupLayout(
|
||||||
state.device,
|
state.device,
|
||||||
&{
|
&{
|
||||||
label = "camera layout",
|
label = "camera layout",
|
||||||
@@ -405,6 +622,50 @@ on_device :: proc "c" (
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Material baked layout
|
||||||
|
|
||||||
|
material_layout_entries := [?]wgpu.BindGroupLayoutEntry {
|
||||||
|
{
|
||||||
|
binding = 0, // slot 0
|
||||||
|
visibility = {.Fragment},
|
||||||
|
buffer = {
|
||||||
|
type = .Uniform,
|
||||||
|
minBindingSize = cast(u64)size_of(Material_Uniform),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
state.material_bind_group_layout = wgpu.DeviceCreateBindGroupLayout(
|
||||||
|
state.device,
|
||||||
|
&{
|
||||||
|
label = "material layout",
|
||||||
|
entryCount = len(material_layout_entries),
|
||||||
|
entries = raw_data(material_layout_entries[:]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Renderable baked layout
|
||||||
|
|
||||||
|
renderable_layout_entries := [?]wgpu.BindGroupLayoutEntry {
|
||||||
|
{
|
||||||
|
binding = 0, // slot 0
|
||||||
|
visibility = {.Vertex},
|
||||||
|
buffer = {
|
||||||
|
type = .Uniform,
|
||||||
|
minBindingSize = cast(u64)size_of(Renderable_Uniform),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
state.renderable_bind_group_layout = wgpu.DeviceCreateBindGroupLayout(
|
||||||
|
state.device,
|
||||||
|
&{
|
||||||
|
label = "renderable layout",
|
||||||
|
entryCount = len(renderable_layout_entries),
|
||||||
|
entries = raw_data(renderable_layout_entries[:]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Diffuse texture + sampler
|
// Diffuse texture + sampler
|
||||||
|
|
||||||
@@ -426,12 +687,16 @@ on_device :: proc "c" (
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Render pipeline
|
// Render pipeline
|
||||||
|
|
||||||
camera_group_layouts := [1]wgpu.BindGroupLayout{state.bind_group_layout}
|
group_layouts := [?]wgpu.BindGroupLayout {
|
||||||
|
state.camera_bind_group_layout,
|
||||||
|
state.material_bind_group_layout,
|
||||||
|
state.renderable_bind_group_layout,
|
||||||
|
}
|
||||||
state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(
|
state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(
|
||||||
state.device,
|
state.device,
|
||||||
&{
|
&{
|
||||||
bindGroupLayoutCount = len(camera_group_layouts),
|
bindGroupLayoutCount = len(group_layouts),
|
||||||
bindGroupLayouts = raw_data(camera_group_layouts[:]),
|
bindGroupLayouts = raw_data(group_layouts[:]),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -471,25 +736,29 @@ on_device :: proc "c" (
|
|||||||
bind_group_entries := [?]wgpu.BindGroupEntry {
|
bind_group_entries := [?]wgpu.BindGroupEntry {
|
||||||
{
|
{
|
||||||
binding = 0, // slot 0
|
binding = 0, // slot 0
|
||||||
buffer = state.uniform_buffer,
|
buffer = state.camera_uniform_buffer,
|
||||||
offset = 0, // offset into buffer to start from
|
offset = 0, // offset into buffer to start from
|
||||||
size = cast(u64)size_of(Camera_Uniform),
|
size = cast(u64)size_of(Camera_Uniform),
|
||||||
},
|
},
|
||||||
{binding = 1, textureView = state.diffuse_texture_view},
|
{binding = 1, textureView = state.diffuse_texture_view},
|
||||||
{binding = 2, sampler = state.diffuse_sampler},
|
{binding = 2, sampler = state.diffuse_sampler},
|
||||||
}
|
}
|
||||||
state.bind_group = wgpu.DeviceCreateBindGroup(
|
state.camera_bind_group = wgpu.DeviceCreateBindGroup(
|
||||||
state.device,
|
state.device,
|
||||||
&{
|
&{
|
||||||
label = "camera bind group",
|
label = "camera bind group",
|
||||||
layout = state.bind_group_layout,
|
layout = state.camera_bind_group_layout,
|
||||||
entryCount = len(bind_group_entries),
|
entryCount = len(bind_group_entries),
|
||||||
entries = raw_data(bind_group_entries[:]),
|
entries = raw_data(bind_group_entries[:]),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initial upload of the matrix
|
// Initial upload of the matrix
|
||||||
upload_camera(state.queue, state.uniform_buffer, framebuffer_aspect())
|
upload_camera(
|
||||||
|
state.queue,
|
||||||
|
state.camera_uniform_buffer,
|
||||||
|
framebuffer_aspect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private = "file")
|
@(private = "file")
|
||||||
@@ -569,9 +838,7 @@ framebuffer_aspect :: proc() -> f32 {
|
|||||||
|
|
||||||
// Perspective projection, WebGPU flavour
|
// Perspective projection, WebGPU flavour
|
||||||
@(private = "file")
|
@(private = "file")
|
||||||
camera_perspective :: proc(
|
camera_perspective :: proc(fovy_degrees, aspect, near, far: f32) -> base.Mat4 {
|
||||||
fovy_degrees, aspect, near, far: f32,
|
|
||||||
) -> linalg.Matrix4f32 {
|
|
||||||
gl := linalg.matrix4_perspective(
|
gl := linalg.matrix4_perspective(
|
||||||
fovy_degrees * math.PI / 180.0,
|
fovy_degrees * math.PI / 180.0,
|
||||||
aspect,
|
aspect,
|
||||||
@@ -580,16 +847,16 @@ camera_perspective :: proc(
|
|||||||
)
|
)
|
||||||
// GL's clip space is [-1, 1] and WebGPU's clip space is [0, 1],
|
// GL's clip space is [-1, 1] and WebGPU's clip space is [0, 1],
|
||||||
// so fold z with a remap: z' = 1/2 z + 1/2 w
|
// so fold z with a remap: z' = 1/2 z + 1/2 w
|
||||||
remap := linalg.identity(linalg.Matrix4f32)
|
remap := linalg.identity(base.Mat4)
|
||||||
remap[2, 2] = 0.5
|
remap[2, 2] = 0.5
|
||||||
remap[2, 3] = 0.5
|
remap[2, 3] = 0.5
|
||||||
return remap * gl
|
return remap * gl
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private = "file")
|
@(private = "file")
|
||||||
camera_mvp :: proc(aspect: f32) -> linalg.Matrix4f32 {
|
camera_mvp :: proc(aspect: f32) -> base.Mat4 {
|
||||||
projection := camera_perspective(60, aspect, 0.1, 100)
|
projection := camera_perspective(60, aspect, 0.1, 100)
|
||||||
view := linalg.matrix4_look_at_f32({0, 0.75, 2}, {0, 0, 0}, {0, 1, 0})
|
view := linalg.matrix4_look_at_f32({0, 2, 5}, {0, 0, 0}, {0, 1, 0})
|
||||||
// model = identity: the loader already centered the model at the origin
|
// model = identity: the loader already centered the model at the origin
|
||||||
return projection * view
|
return projection * view
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user