mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-02 12:48:14 +00:00
wgpu: move examples to examples repo
This commit is contained in:
+20
-16
@@ -1,17 +1,18 @@
|
|||||||
# WGPU
|
/*
|
||||||
|
|
||||||
A cross-platform (and WASM) GPU API.
|
A cross-platform (and WASM) GPU API.
|
||||||
|
|
||||||
WASM support is achieved by providing wrappers around the browser native WebGPU API
|
WASM support is achieved by providing wrappers around the browser native WebGPU API
|
||||||
that are called instead of the [wgpu-native](https://github.com/gfx-rs/wgpu-native) library,
|
that are called instead of the [wgpu-native](https://github.com/gfx-rs/wgpu-native) library,
|
||||||
the wgpu-native library provides support for all other targets.
|
the wgpu-native library provides support for all other targets.
|
||||||
|
|
||||||
Have a look at the `example/` directory for the rendering of a basic triangle.
|
**Examples**
|
||||||
|
|
||||||
## Getting the wgpu-native libraries
|
You can find a number of examples on [[Odin's official examples repository; https://github.com/odin-lang/examples/tree/master/wgpu]].
|
||||||
|
|
||||||
|
**Getting the wgpu-native libraries**
|
||||||
|
|
||||||
For native support (not the browser), some libraries are required. Fortunately this is
|
For native support (not the browser), some libraries are required. Fortunately this is
|
||||||
extremely easy, just download them from the [releases on GitHub](https://github.com/gfx-rs/wgpu-native/releases/tag/v24.0.0.2),
|
extremely easy, just download them from the [[releases on GitHub; https://github.com/gfx-rs/wgpu-native/releases/tag/v24.0.0.2]].
|
||||||
the bindings are for v24.0.0.2 at the moment.
|
the bindings are for v24.0.0.2 at the moment.
|
||||||
|
|
||||||
These are expected in the `lib` folder under the same name as they are released (just unzipped).
|
These are expected in the `lib` folder under the same name as they are released (just unzipped).
|
||||||
@@ -19,7 +20,7 @@ By default it will look for a static release version (`wgpu-OS-ARCH-release.a|li
|
|||||||
you can set `-define:WGPU_DEBUG=true` for it to look for a debug version,
|
you can set `-define:WGPU_DEBUG=true` for it to look for a debug version,
|
||||||
and use `-define:WGPU_SHARED=true` to look for the shared libraries.
|
and use `-define:WGPU_SHARED=true` to look for the shared libraries.
|
||||||
|
|
||||||
## WASM
|
**WASM**
|
||||||
|
|
||||||
For WASM, the module has to be built with a function table to enable callbacks.
|
For WASM, the module has to be built with a function table to enable callbacks.
|
||||||
You can do so using `-extra-linker-flags:"--export-table"`.
|
You can do so using `-extra-linker-flags:"--export-table"`.
|
||||||
@@ -29,12 +30,20 @@ Being able to allocate is also required (for some auxiliary APIs but also for ma
|
|||||||
You can set the context that is used for allocations by setting the global variable `wpgu.g_context`.
|
You can set the context that is used for allocations by setting the global variable `wpgu.g_context`.
|
||||||
It will default to the `runtime.default_context`.
|
It will default to the `runtime.default_context`.
|
||||||
|
|
||||||
Again, have a look at the `example/` and how it is set up, doing the `--import-memory` and the likes
|
Have a look at the [[example build file; https://github.com/odin-lang/examples/blob/master/wgpu/glfw-triangle/build_web.sh]] and [[html file; https://github.com/odin-lang/examples/blob/master/wgpu/glfw-triangle/web/index.html]]
|
||||||
|
to see how it looks when set up, doing the `--import-memory` and the likes
|
||||||
is not strictly necessary but allows your app more memory than the minimal default.
|
is not strictly necessary but allows your app more memory than the minimal default.
|
||||||
|
|
||||||
The bindings work on both `-target:js_wasm32` and `-target:js_wasm64p32`.
|
The bindings work on both `-target:js_wasm32` and `-target:js_wasm64p32`.
|
||||||
|
|
||||||
## GLFW Glue
|
**SDL Glue**
|
||||||
|
|
||||||
|
There is an inner package `sdl2glue` (and `sdl3glue`) that can be used to glue together WGPU and SDL.
|
||||||
|
It exports one procedure `GetSurface(wgpu.Instance, ^SDL.Window) -> wgpu.Surface`.
|
||||||
|
The procedure will call the needed target specific procedures and return a surface configured
|
||||||
|
for the given window.
|
||||||
|
|
||||||
|
**GLFW Glue**
|
||||||
|
|
||||||
There is an inner package `glfwglue` that can be used to glue together WGPU and GLFW.
|
There is an inner package `glfwglue` that can be used to glue together WGPU and GLFW.
|
||||||
It exports one procedure `GetSurface(wgpu.Instance, glfw.WindowHandle) -> wgpu.Surface`.
|
It exports one procedure `GetSurface(wgpu.Instance, glfw.WindowHandle) -> wgpu.Surface`.
|
||||||
@@ -44,14 +53,7 @@ for the given window.
|
|||||||
Do note that wgpu does not require GLFW, you can use native windows or another windowing library too.
|
Do note that wgpu does not require GLFW, you can use native windows or another windowing library too.
|
||||||
For that you can take inspiration from `glfwglue` on glueing them together.
|
For that you can take inspiration from `glfwglue` on glueing them together.
|
||||||
|
|
||||||
## SDL2 Glue
|
**GLFW and Wayland**
|
||||||
|
|
||||||
There is an inner package `sdl2glue` that can be used to glue together WGPU and SDL2.
|
|
||||||
It exports one procedure `GetSurface(wgpu.Instance, ^sdl2.Window) -> wgpu.Surface`.
|
|
||||||
The procedure will call the needed target specific procedures and return a surface configured
|
|
||||||
for the given window.
|
|
||||||
|
|
||||||
### Wayland
|
|
||||||
|
|
||||||
GLFW supports Wayland from version 3.4 onwards and only if it is compiled with `-DGLFW_EXPOSE_NATIVE_WAYLAND`.
|
GLFW supports Wayland from version 3.4 onwards and only if it is compiled with `-DGLFW_EXPOSE_NATIVE_WAYLAND`.
|
||||||
|
|
||||||
@@ -59,3 +61,5 @@ Odin links against your system's glfw library (probably installed through a pack
|
|||||||
If that version is lower than 3.4 or hasn't been compiled with the previously mentioned define,
|
If that version is lower than 3.4 or hasn't been compiled with the previously mentioned define,
|
||||||
you will have to compile glfw from source yourself and adjust the `foreign import` declarations in `vendor:glfw/bindings` to
|
you will have to compile glfw from source yourself and adjust the `foreign import` declarations in `vendor:glfw/bindings` to
|
||||||
point to it.
|
point to it.
|
||||||
|
*/
|
||||||
|
package wgpu
|
||||||
Vendored
-17
@@ -1,17 +0,0 @@
|
|||||||
FILES := $(wildcard *)
|
|
||||||
|
|
||||||
# NOTE: changing this requires changing the same values in the `web/index.html`.
|
|
||||||
INITIAL_MEMORY_PAGES := 2000
|
|
||||||
MAX_MEMORY_PAGES := 65536
|
|
||||||
|
|
||||||
PAGE_SIZE := 65536
|
|
||||||
INITIAL_MEMORY_BYTES := $(shell expr $(INITIAL_MEMORY_PAGES) \* $(PAGE_SIZE))
|
|
||||||
MAX_MEMORY_BYTES := $(shell expr $(MAX_MEMORY_PAGES) \* $(PAGE_SIZE))
|
|
||||||
|
|
||||||
web/triangle.wasm: $(FILES) ../../wgpu.js ../../../../core/sys/wasm/js/odin.js
|
|
||||||
odin build . \
|
|
||||||
-target:js_wasm32 -out:web/triangle.wasm -o:size \
|
|
||||||
-extra-linker-flags:"--export-table --import-memory --initial-memory=$(INITIAL_MEMORY_BYTES) --max-memory=$(MAX_MEMORY_BYTES)"
|
|
||||||
|
|
||||||
cp ../../wgpu.js web/wgpu.js
|
|
||||||
cp ../../../../core/sys/wasm/js/odin.js web/odin.js
|
|
||||||
Vendored
-12
@@ -1,12 +0,0 @@
|
|||||||
REM NOTE: changing this requires changing the same values in the `web/index.html`.
|
|
||||||
set INITIAL_MEMORY_PAGES=2000
|
|
||||||
set MAX_MEMORY_PAGES=65536
|
|
||||||
|
|
||||||
set PAGE_SIZE=65536
|
|
||||||
set /a INITIAL_MEMORY_BYTES=%INITIAL_MEMORY_PAGES% * %PAGE_SIZE%
|
|
||||||
set /a MAX_MEMORY_BYTES=%MAX_MEMORY_PAGES% * %PAGE_SIZE%
|
|
||||||
|
|
||||||
call odin.exe build . -target:js_wasm32 -out:web/triangle.wasm -o:size -extra-linker-flags:"--export-table --import-memory --initial-memory=%INITIAL_MEMORY_BYTES% --max-memory=%MAX_MEMORY_BYTES%"
|
|
||||||
|
|
||||||
copy "..\..\wgpu.js" "web\wgpu.js"
|
|
||||||
copy "..\..\..\..\core\sys\wasm\js\odin.js" "web\odin.js"
|
|
||||||
Vendored
-189
@@ -1,189 +0,0 @@
|
|||||||
package vendor_wgpu_example_triangle
|
|
||||||
|
|
||||||
import "base:runtime"
|
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
|
|
||||||
import "vendor:wgpu"
|
|
||||||
|
|
||||||
State :: struct {
|
|
||||||
ctx: runtime.Context,
|
|
||||||
os: OS,
|
|
||||||
|
|
||||||
instance: wgpu.Instance,
|
|
||||||
surface: wgpu.Surface,
|
|
||||||
adapter: wgpu.Adapter,
|
|
||||||
device: wgpu.Device,
|
|
||||||
config: wgpu.SurfaceConfiguration,
|
|
||||||
queue: wgpu.Queue,
|
|
||||||
module: wgpu.ShaderModule,
|
|
||||||
pipeline_layout: wgpu.PipelineLayout,
|
|
||||||
pipeline: wgpu.RenderPipeline,
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
state: State
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
state.ctx = context
|
|
||||||
|
|
||||||
os_init(&state.os)
|
|
||||||
|
|
||||||
state.instance = wgpu.CreateInstance(nil)
|
|
||||||
if state.instance == nil {
|
|
||||||
panic("WebGPU is not supported")
|
|
||||||
}
|
|
||||||
state.surface = os_get_surface(&state.os, state.instance)
|
|
||||||
|
|
||||||
wgpu.InstanceRequestAdapter(state.instance, &{ compatibleSurface = state.surface }, { callback = on_adapter })
|
|
||||||
|
|
||||||
on_adapter :: proc "c" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, message: string, userdata1: rawptr, userdata2: rawptr) {
|
|
||||||
context = state.ctx
|
|
||||||
if status != .Success || adapter == nil {
|
|
||||||
fmt.panicf("request adapter failure: [%v] %s", status, message)
|
|
||||||
}
|
|
||||||
state.adapter = adapter
|
|
||||||
wgpu.AdapterRequestDevice(adapter, nil, { callback = on_device })
|
|
||||||
}
|
|
||||||
|
|
||||||
on_device :: proc "c" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, message: string, userdata1: rawptr, userdata2: rawptr) {
|
|
||||||
context = state.ctx
|
|
||||||
if status != .Success || device == nil {
|
|
||||||
fmt.panicf("request device failure: [%v] %s", status, message)
|
|
||||||
}
|
|
||||||
state.device = device
|
|
||||||
|
|
||||||
width, height := os_get_render_bounds(&state.os)
|
|
||||||
|
|
||||||
state.config = wgpu.SurfaceConfiguration {
|
|
||||||
device = state.device,
|
|
||||||
usage = { .RenderAttachment },
|
|
||||||
format = .BGRA8Unorm,
|
|
||||||
width = width,
|
|
||||||
height = height,
|
|
||||||
presentMode = .Fifo,
|
|
||||||
alphaMode = .Opaque,
|
|
||||||
}
|
|
||||||
wgpu.SurfaceConfigure(state.surface, &state.config)
|
|
||||||
|
|
||||||
state.queue = wgpu.DeviceGetQueue(state.device)
|
|
||||||
|
|
||||||
shader :: `
|
|
||||||
@vertex
|
|
||||||
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
|
||||||
let x = f32(i32(in_vertex_index) - 1);
|
|
||||||
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
|
||||||
return vec4<f32>(x, y, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main() -> @location(0) vec4<f32> {
|
|
||||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}`
|
|
||||||
|
|
||||||
state.module = wgpu.DeviceCreateShaderModule(state.device, &{
|
|
||||||
nextInChain = &wgpu.ShaderSourceWGSL{
|
|
||||||
sType = .ShaderSourceWGSL,
|
|
||||||
code = shader,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{})
|
|
||||||
state.pipeline = wgpu.DeviceCreateRenderPipeline(state.device, &{
|
|
||||||
layout = state.pipeline_layout,
|
|
||||||
vertex = {
|
|
||||||
module = state.module,
|
|
||||||
entryPoint = "vs_main",
|
|
||||||
},
|
|
||||||
fragment = &{
|
|
||||||
module = state.module,
|
|
||||||
entryPoint = "fs_main",
|
|
||||||
targetCount = 1,
|
|
||||||
targets = &wgpu.ColorTargetState{
|
|
||||||
format = .BGRA8Unorm,
|
|
||||||
writeMask = wgpu.ColorWriteMaskFlags_All,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
primitive = {
|
|
||||||
topology = .TriangleList,
|
|
||||||
|
|
||||||
},
|
|
||||||
multisample = {
|
|
||||||
count = 1,
|
|
||||||
mask = 0xFFFFFFFF,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
os_run(&state.os)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resize :: proc "c" () {
|
|
||||||
context = state.ctx
|
|
||||||
|
|
||||||
state.config.width, state.config.height = os_get_render_bounds(&state.os)
|
|
||||||
wgpu.SurfaceConfigure(state.surface, &state.config)
|
|
||||||
}
|
|
||||||
|
|
||||||
frame :: proc "c" (dt: f32) {
|
|
||||||
context = state.ctx
|
|
||||||
|
|
||||||
surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface)
|
|
||||||
switch surface_texture.status {
|
|
||||||
case .SuccessOptimal, .SuccessSuboptimal:
|
|
||||||
// All good, could handle suboptimal here.
|
|
||||||
case .Timeout, .Outdated, .Lost:
|
|
||||||
// Skip this frame, and re-configure surface.
|
|
||||||
if surface_texture.texture != nil {
|
|
||||||
wgpu.TextureRelease(surface_texture.texture)
|
|
||||||
}
|
|
||||||
resize()
|
|
||||||
return
|
|
||||||
case .OutOfMemory, .DeviceLost, .Error:
|
|
||||||
// Fatal error
|
|
||||||
fmt.panicf("[triangle] get_current_texture status=%v", surface_texture.status)
|
|
||||||
}
|
|
||||||
defer wgpu.TextureRelease(surface_texture.texture)
|
|
||||||
|
|
||||||
frame := wgpu.TextureCreateView(surface_texture.texture, nil)
|
|
||||||
defer wgpu.TextureViewRelease(frame)
|
|
||||||
|
|
||||||
command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil)
|
|
||||||
defer wgpu.CommandEncoderRelease(command_encoder)
|
|
||||||
|
|
||||||
render_pass_encoder := wgpu.CommandEncoderBeginRenderPass(
|
|
||||||
command_encoder, &{
|
|
||||||
colorAttachmentCount = 1,
|
|
||||||
colorAttachments = &wgpu.RenderPassColorAttachment{
|
|
||||||
view = frame,
|
|
||||||
loadOp = .Clear,
|
|
||||||
storeOp = .Store,
|
|
||||||
depthSlice = wgpu.DEPTH_SLICE_UNDEFINED,
|
|
||||||
clearValue = { 0, 1, 0, 1 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
|
|
||||||
wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0)
|
|
||||||
|
|
||||||
wgpu.RenderPassEncoderEnd(render_pass_encoder)
|
|
||||||
wgpu.RenderPassEncoderRelease(render_pass_encoder)
|
|
||||||
|
|
||||||
command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil)
|
|
||||||
defer wgpu.CommandBufferRelease(command_buffer)
|
|
||||||
|
|
||||||
wgpu.QueueSubmit(state.queue, { command_buffer })
|
|
||||||
wgpu.SurfacePresent(state.surface)
|
|
||||||
}
|
|
||||||
|
|
||||||
finish :: proc() {
|
|
||||||
wgpu.RenderPipelineRelease(state.pipeline)
|
|
||||||
wgpu.PipelineLayoutRelease(state.pipeline_layout)
|
|
||||||
wgpu.ShaderModuleRelease(state.module)
|
|
||||||
wgpu.QueueRelease(state.queue)
|
|
||||||
wgpu.DeviceRelease(state.device)
|
|
||||||
wgpu.AdapterRelease(state.adapter)
|
|
||||||
wgpu.SurfaceRelease(state.surface)
|
|
||||||
wgpu.InstanceRelease(state.instance)
|
|
||||||
}
|
|
||||||
-55
@@ -1,55 +0,0 @@
|
|||||||
#+build !js
|
|
||||||
package vendor_wgpu_example_triangle
|
|
||||||
|
|
||||||
import "core:time"
|
|
||||||
|
|
||||||
import "vendor:glfw"
|
|
||||||
import "vendor:wgpu"
|
|
||||||
import "vendor:wgpu/glfwglue"
|
|
||||||
|
|
||||||
OS :: struct {
|
|
||||||
window: glfw.WindowHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
os_init :: proc(os: ^OS) {
|
|
||||||
if !glfw.Init() {
|
|
||||||
panic("[glfw] init failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
|
|
||||||
os.window = glfw.CreateWindow(960, 540, "WGPU Native Triangle", nil, nil)
|
|
||||||
|
|
||||||
glfw.SetFramebufferSizeCallback(os.window, size_callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_run :: proc(os: ^OS) {
|
|
||||||
dt: f32
|
|
||||||
|
|
||||||
for !glfw.WindowShouldClose(os.window) {
|
|
||||||
start := time.tick_now()
|
|
||||||
|
|
||||||
glfw.PollEvents()
|
|
||||||
frame(dt)
|
|
||||||
|
|
||||||
dt = f32(time.duration_seconds(time.tick_since(start)))
|
|
||||||
}
|
|
||||||
|
|
||||||
finish()
|
|
||||||
|
|
||||||
glfw.DestroyWindow(os.window)
|
|
||||||
glfw.Terminate()
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) {
|
|
||||||
iw, ih := glfw.GetWindowSize(os.window)
|
|
||||||
return u32(iw), u32(ih)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface {
|
|
||||||
return glfwglue.GetSurface(instance, os.window)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) {
|
|
||||||
resize()
|
|
||||||
}
|
|
||||||
Vendored
-61
@@ -1,61 +0,0 @@
|
|||||||
package vendor_wgpu_example_triangle
|
|
||||||
|
|
||||||
import "core:sys/wasm/js"
|
|
||||||
|
|
||||||
import "vendor:wgpu"
|
|
||||||
|
|
||||||
OS :: struct {
|
|
||||||
initialized: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
g_os: ^OS
|
|
||||||
|
|
||||||
os_init :: proc(os: ^OS) {
|
|
||||||
g_os = os
|
|
||||||
assert(js.add_window_event_listener(.Resize, nil, size_callback))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: frame loop is done by the runtime.js repeatedly calling `step`.
|
|
||||||
os_run :: proc(os: ^OS) {
|
|
||||||
os.initialized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) {
|
|
||||||
rect := js.get_bounding_client_rect("body")
|
|
||||||
return u32(rect.width), u32(rect.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface {
|
|
||||||
return wgpu.InstanceCreateSurface(
|
|
||||||
instance,
|
|
||||||
&wgpu.SurfaceDescriptor{
|
|
||||||
nextInChain = &wgpu.SurfaceSourceCanvasHTMLSelector{
|
|
||||||
sType = .SurfaceSourceCanvasHTMLSelector,
|
|
||||||
selector = "#wgpu-canvas",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file", export)
|
|
||||||
step :: proc(dt: f32) -> bool {
|
|
||||||
if !g_os.initialized {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
frame(dt)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file", fini)
|
|
||||||
os_fini :: proc() {
|
|
||||||
js.remove_window_event_listener(.Resize, nil, size_callback)
|
|
||||||
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
size_callback :: proc(e: js.Event) {
|
|
||||||
resize()
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" style="height: 100%;">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>WGPU WASM Triangle</title>
|
|
||||||
</head>
|
|
||||||
<body id="body" style="height: 100%; padding: 0; margin: 0; overflow: hidden;">
|
|
||||||
<canvas id="wgpu-canvas"></canvas>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="odin.js"></script>
|
|
||||||
<script type="text/javascript" src="wgpu.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
const mem = new WebAssembly.Memory({ initial: 2000, maximum: 65536, shared: false });
|
|
||||||
const memInterface = new odin.WasmMemoryInterface();
|
|
||||||
memInterface.setMemory(mem);
|
|
||||||
|
|
||||||
const wgpuInterface = new odin.WebGPUInterface(memInterface);
|
|
||||||
|
|
||||||
odin.runWasm("triangle.wasm", null, { wgpu: wgpuInterface.getInterface() }, memInterface, /*intSize=8*/);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Vendored
-17
@@ -1,17 +0,0 @@
|
|||||||
FILES := $(wildcard *)
|
|
||||||
|
|
||||||
# NOTE: changing this requires changing the same values in the `web/index.html`.
|
|
||||||
INITIAL_MEMORY_PAGES := 2000
|
|
||||||
MAX_MEMORY_PAGES := 65536
|
|
||||||
|
|
||||||
PAGE_SIZE := 65536
|
|
||||||
INITIAL_MEMORY_BYTES := $(shell expr $(INITIAL_MEMORY_PAGES) \* $(PAGE_SIZE))
|
|
||||||
MAX_MEMORY_BYTES := $(shell expr $(MAX_MEMORY_PAGES) \* $(PAGE_SIZE))
|
|
||||||
|
|
||||||
web/triangle.wasm: $(FILES) ../../wgpu.js ../../../../core/sys/wasm/js/odin.js
|
|
||||||
odin build . \
|
|
||||||
-target:js_wasm32 -out:web/triangle.wasm -o:size \
|
|
||||||
-extra-linker-flags:"--export-table --import-memory --initial-memory=$(INITIAL_MEMORY_BYTES) --max-memory=$(MAX_MEMORY_BYTES)"
|
|
||||||
|
|
||||||
cp ../../wgpu.js web/wgpu.js
|
|
||||||
cp ../../../../core/sys/wasm/js/odin.js web/odin.js
|
|
||||||
Vendored
-12
@@ -1,12 +0,0 @@
|
|||||||
REM NOTE: changing this requires changing the same values in the `web/index.html`.
|
|
||||||
set INITIAL_MEMORY_PAGES=2000
|
|
||||||
set MAX_MEMORY_PAGES=65536
|
|
||||||
|
|
||||||
set PAGE_SIZE=65536
|
|
||||||
set /a INITIAL_MEMORY_BYTES=%INITIAL_MEMORY_PAGES% * %PAGE_SIZE%
|
|
||||||
set /a MAX_MEMORY_BYTES=%MAX_MEMORY_PAGES% * %PAGE_SIZE%
|
|
||||||
|
|
||||||
call odin.exe build . -target:js_wasm32 -out:web/triangle.wasm -o:size -extra-linker-flags:"--export-table --import-memory --initial-memory=%INITIAL_MEMORY_BYTES% --max-memory=%MAX_MEMORY_BYTES%"
|
|
||||||
|
|
||||||
copy "..\..\wgpu.js" "web\wgpu.js"
|
|
||||||
copy "..\..\..\..\core\sys\wasm\js\odin.js" "web\odin.js"
|
|
||||||
Vendored
-189
@@ -1,189 +0,0 @@
|
|||||||
package vendor_wgpu_example_triangle
|
|
||||||
|
|
||||||
import "base:runtime"
|
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
|
|
||||||
import "vendor:wgpu"
|
|
||||||
|
|
||||||
State :: struct {
|
|
||||||
ctx: runtime.Context,
|
|
||||||
os: OS,
|
|
||||||
|
|
||||||
instance: wgpu.Instance,
|
|
||||||
surface: wgpu.Surface,
|
|
||||||
adapter: wgpu.Adapter,
|
|
||||||
device: wgpu.Device,
|
|
||||||
config: wgpu.SurfaceConfiguration,
|
|
||||||
queue: wgpu.Queue,
|
|
||||||
module: wgpu.ShaderModule,
|
|
||||||
pipeline_layout: wgpu.PipelineLayout,
|
|
||||||
pipeline: wgpu.RenderPipeline,
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
state: State
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
state.ctx = context
|
|
||||||
|
|
||||||
os_init(&state.os)
|
|
||||||
|
|
||||||
state.instance = wgpu.CreateInstance(nil)
|
|
||||||
if state.instance == nil {
|
|
||||||
panic("WebGPU is not supported")
|
|
||||||
}
|
|
||||||
state.surface = os_get_surface(&state.os, state.instance)
|
|
||||||
|
|
||||||
wgpu.InstanceRequestAdapter(state.instance, &{ compatibleSurface = state.surface }, { callback = on_adapter })
|
|
||||||
|
|
||||||
on_adapter :: proc "c" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, message: string, userdata1, userdata2: rawptr) {
|
|
||||||
context = state.ctx
|
|
||||||
if status != .Success || adapter == nil {
|
|
||||||
fmt.panicf("request adapter failure: [%v] %s", status, message)
|
|
||||||
}
|
|
||||||
state.adapter = adapter
|
|
||||||
wgpu.AdapterRequestDevice(adapter, nil, { callback = on_device })
|
|
||||||
}
|
|
||||||
|
|
||||||
on_device :: proc "c" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, message: string, userdata1, userdata2: rawptr) {
|
|
||||||
context = state.ctx
|
|
||||||
if status != .Success || device == nil {
|
|
||||||
fmt.panicf("request device failure: [%v] %s", status, message)
|
|
||||||
}
|
|
||||||
state.device = device
|
|
||||||
|
|
||||||
width, height := os_get_render_bounds(&state.os)
|
|
||||||
|
|
||||||
state.config = wgpu.SurfaceConfiguration {
|
|
||||||
device = state.device,
|
|
||||||
usage = { .RenderAttachment },
|
|
||||||
format = .BGRA8Unorm,
|
|
||||||
width = width,
|
|
||||||
height = height,
|
|
||||||
presentMode = .Fifo,
|
|
||||||
alphaMode = .Opaque,
|
|
||||||
}
|
|
||||||
wgpu.SurfaceConfigure(state.surface, &state.config)
|
|
||||||
|
|
||||||
state.queue = wgpu.DeviceGetQueue(state.device)
|
|
||||||
|
|
||||||
shader :: `
|
|
||||||
@vertex
|
|
||||||
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
|
||||||
let x = f32(i32(in_vertex_index) - 1);
|
|
||||||
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
|
||||||
return vec4<f32>(x, y, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main() -> @location(0) vec4<f32> {
|
|
||||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}`
|
|
||||||
|
|
||||||
state.module = wgpu.DeviceCreateShaderModule(state.device, &{
|
|
||||||
nextInChain = &wgpu.ShaderSourceWGSL{
|
|
||||||
sType = .ShaderSourceWGSL,
|
|
||||||
code = shader,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{})
|
|
||||||
state.pipeline = wgpu.DeviceCreateRenderPipeline(state.device, &{
|
|
||||||
layout = state.pipeline_layout,
|
|
||||||
vertex = {
|
|
||||||
module = state.module,
|
|
||||||
entryPoint = "vs_main",
|
|
||||||
},
|
|
||||||
fragment = &{
|
|
||||||
module = state.module,
|
|
||||||
entryPoint = "fs_main",
|
|
||||||
targetCount = 1,
|
|
||||||
targets = &wgpu.ColorTargetState{
|
|
||||||
format = .BGRA8Unorm,
|
|
||||||
writeMask = wgpu.ColorWriteMaskFlags_All,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
primitive = {
|
|
||||||
topology = .TriangleList,
|
|
||||||
|
|
||||||
},
|
|
||||||
multisample = {
|
|
||||||
count = 1,
|
|
||||||
mask = 0xFFFFFFFF,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
os_run(&state.os)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resize :: proc "c" () {
|
|
||||||
context = state.ctx
|
|
||||||
|
|
||||||
state.config.width, state.config.height = os_get_render_bounds(&state.os)
|
|
||||||
wgpu.SurfaceConfigure(state.surface, &state.config)
|
|
||||||
}
|
|
||||||
|
|
||||||
frame :: proc "c" (dt: f32) {
|
|
||||||
context = state.ctx
|
|
||||||
|
|
||||||
surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface)
|
|
||||||
switch surface_texture.status {
|
|
||||||
case .SuccessOptimal, .SuccessSuboptimal:
|
|
||||||
// All good, could handle suboptimal here.
|
|
||||||
case .Timeout, .Outdated, .Lost:
|
|
||||||
// Skip this frame, and re-configure surface.
|
|
||||||
if surface_texture.texture != nil {
|
|
||||||
wgpu.TextureRelease(surface_texture.texture)
|
|
||||||
}
|
|
||||||
resize()
|
|
||||||
return
|
|
||||||
case .OutOfMemory, .DeviceLost, .Error:
|
|
||||||
// Fatal error
|
|
||||||
fmt.panicf("[triangle] get_current_texture status=%v", surface_texture.status)
|
|
||||||
}
|
|
||||||
defer wgpu.TextureRelease(surface_texture.texture)
|
|
||||||
|
|
||||||
frame := wgpu.TextureCreateView(surface_texture.texture, nil)
|
|
||||||
defer wgpu.TextureViewRelease(frame)
|
|
||||||
|
|
||||||
command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil)
|
|
||||||
defer wgpu.CommandEncoderRelease(command_encoder)
|
|
||||||
|
|
||||||
render_pass_encoder := wgpu.CommandEncoderBeginRenderPass(
|
|
||||||
command_encoder, &{
|
|
||||||
colorAttachmentCount = 1,
|
|
||||||
colorAttachments = &wgpu.RenderPassColorAttachment{
|
|
||||||
view = frame,
|
|
||||||
loadOp = .Clear,
|
|
||||||
storeOp = .Store,
|
|
||||||
depthSlice = wgpu.DEPTH_SLICE_UNDEFINED,
|
|
||||||
clearValue = { 0, 1, 0, 1 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
|
|
||||||
wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0)
|
|
||||||
|
|
||||||
wgpu.RenderPassEncoderEnd(render_pass_encoder)
|
|
||||||
wgpu.RenderPassEncoderRelease(render_pass_encoder)
|
|
||||||
|
|
||||||
command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil)
|
|
||||||
defer wgpu.CommandBufferRelease(command_buffer)
|
|
||||||
|
|
||||||
wgpu.QueueSubmit(state.queue, { command_buffer })
|
|
||||||
wgpu.SurfacePresent(state.surface)
|
|
||||||
}
|
|
||||||
|
|
||||||
finish :: proc() {
|
|
||||||
wgpu.RenderPipelineRelease(state.pipeline)
|
|
||||||
wgpu.PipelineLayoutRelease(state.pipeline_layout)
|
|
||||||
wgpu.ShaderModuleRelease(state.module)
|
|
||||||
wgpu.QueueRelease(state.queue)
|
|
||||||
wgpu.DeviceRelease(state.device)
|
|
||||||
wgpu.AdapterRelease(state.adapter)
|
|
||||||
wgpu.SurfaceRelease(state.surface)
|
|
||||||
wgpu.InstanceRelease(state.instance)
|
|
||||||
}
|
|
||||||
Vendored
-61
@@ -1,61 +0,0 @@
|
|||||||
package vendor_wgpu_example_triangle
|
|
||||||
|
|
||||||
import "core:sys/wasm/js"
|
|
||||||
|
|
||||||
import "vendor:wgpu"
|
|
||||||
|
|
||||||
OS :: struct {
|
|
||||||
initialized: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
g_os: ^OS
|
|
||||||
|
|
||||||
os_init :: proc(os: ^OS) {
|
|
||||||
g_os = os
|
|
||||||
assert(js.add_window_event_listener(.Resize, nil, size_callback))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: frame loop is done by the runtime.js repeatedly calling `step`.
|
|
||||||
os_run :: proc(os: ^OS) {
|
|
||||||
os.initialized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) {
|
|
||||||
rect := js.get_bounding_client_rect("body")
|
|
||||||
return u32(rect.width), u32(rect.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface {
|
|
||||||
return wgpu.InstanceCreateSurface(
|
|
||||||
instance,
|
|
||||||
&wgpu.SurfaceDescriptor{
|
|
||||||
nextInChain = &wgpu.SurfaceSourceCanvasHTMLSelector{
|
|
||||||
sType = .SurfaceSourceCanvasHTMLSelector,
|
|
||||||
selector = "#wgpu-canvas",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file", export)
|
|
||||||
step :: proc(dt: f32) -> bool {
|
|
||||||
if !g_os.initialized {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
frame(dt)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file", fini)
|
|
||||||
os_fini :: proc() {
|
|
||||||
js.remove_window_event_listener(.Resize, nil, size_callback)
|
|
||||||
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
size_callback :: proc(e: js.Event) {
|
|
||||||
resize()
|
|
||||||
}
|
|
||||||
-85
@@ -1,85 +0,0 @@
|
|||||||
#+build !js
|
|
||||||
package vendor_wgpu_example_triangle
|
|
||||||
|
|
||||||
import "core:c"
|
|
||||||
import "core:fmt"
|
|
||||||
|
|
||||||
import "vendor:sdl2"
|
|
||||||
import "vendor:wgpu"
|
|
||||||
import "vendor:wgpu/sdl2glue"
|
|
||||||
|
|
||||||
OS :: struct {
|
|
||||||
window: ^sdl2.Window,
|
|
||||||
}
|
|
||||||
|
|
||||||
os_init :: proc(os: ^OS) {
|
|
||||||
sdl_flags := sdl2.InitFlags{.VIDEO, .JOYSTICK, .GAMECONTROLLER, .EVENTS}
|
|
||||||
if res := sdl2.Init(sdl_flags); res != 0 {
|
|
||||||
fmt.eprintfln("ERROR: Failed to initialize SDL: [%s]", sdl2.GetError())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
window_flags: sdl2.WindowFlags = {.SHOWN, .ALLOW_HIGHDPI, .RESIZABLE}
|
|
||||||
os.window = sdl2.CreateWindow(
|
|
||||||
"WGPU Native Triangle",
|
|
||||||
sdl2.WINDOWPOS_CENTERED,
|
|
||||||
sdl2.WINDOWPOS_CENTERED,
|
|
||||||
960,
|
|
||||||
540,
|
|
||||||
window_flags,
|
|
||||||
)
|
|
||||||
if os.window == nil {
|
|
||||||
fmt.eprintfln("ERROR: Failed to create the SDL Window: [%s]", sdl2.GetError())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sdl2.AddEventWatch(size_callback, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_run :: proc(os: ^OS) {
|
|
||||||
now := sdl2.GetPerformanceCounter()
|
|
||||||
last : u64
|
|
||||||
dt: f32
|
|
||||||
main_loop: for {
|
|
||||||
last = now
|
|
||||||
now = sdl2.GetPerformanceCounter()
|
|
||||||
dt = f32((now - last) * 1000) / f32(sdl2.GetPerformanceFrequency())
|
|
||||||
|
|
||||||
e: sdl2.Event
|
|
||||||
|
|
||||||
for sdl2.PollEvent(&e) {
|
|
||||||
#partial switch (e.type) {
|
|
||||||
case .QUIT:
|
|
||||||
break main_loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
frame(dt)
|
|
||||||
}
|
|
||||||
|
|
||||||
sdl2.DestroyWindow(os.window)
|
|
||||||
sdl2.Quit()
|
|
||||||
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) {
|
|
||||||
iw, ih: c.int
|
|
||||||
sdl2.GetWindowSize(os.window, &iw, &ih)
|
|
||||||
return u32(iw), u32(ih)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface {
|
|
||||||
return sdl2glue.GetSurface(instance, os.window)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
size_callback :: proc "c" (userdata: rawptr, event: ^sdl2.Event) -> c.int {
|
|
||||||
if event.type == .WINDOWEVENT {
|
|
||||||
if event.window.event == .SIZE_CHANGED || event.window.event == .RESIZED {
|
|
||||||
resize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" style="height: 100%;">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>WGPU WASM Triangle</title>
|
|
||||||
</head>
|
|
||||||
<body id="body" style="height: 100%; padding: 0; margin: 0; overflow: hidden;">
|
|
||||||
<canvas id="wgpu-canvas"></canvas>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="odin.js"></script>
|
|
||||||
<script type="text/javascript" src="wgpu.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
const mem = new WebAssembly.Memory({ initial: 2000, maximum: 65536, shared: false });
|
|
||||||
const memInterface = new odin.WasmMemoryInterface();
|
|
||||||
memInterface.setMemory(mem);
|
|
||||||
|
|
||||||
const wgpuInterface = new odin.WebGPUInterface(memInterface);
|
|
||||||
|
|
||||||
odin.runWasm("triangle.wasm", null, { wgpu: wgpuInterface.getInterface() }, memInterface, /*intSize=8*/);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user