diff --git a/README.md b/README.md index d998da3..4ccefaa 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ import { createWindow, getProcAddress, mainloop, -} from "https://deno.land/x/dwm@0.3.3/mod.ts"; +} from "https://deno.land/x/dwm@0.3.4/mod.ts"; import * as gl from "https://deno.land/x/gluten@0.1.3/api/gles23.2.ts"; const window = createWindow({ @@ -51,7 +51,7 @@ await mainloop(frame); import { mainloop, WindowCanvas, -} from "https://deno.land/x/dwm@0.3.3/ext/canvas.ts"; +} from "https://deno.land/x/dwm@0.3.4/ext/canvas.ts"; const canvas = new WindowCanvas({ title: "Skia Canvas", @@ -99,10 +99,10 @@ deno run --unstable --allow-ffi --allow-write --allow-env ## Maintainers - Dj ([@DjDeveloperr](https://github.com/DjDeveloperr)) -- Loading ([@load1n9](https://github.com/load1n9)) +- Dean Srebnik ([@load1n9](https://github.com/load1n9)) ## License [Apache-2.0](./LICENSE) licensed. -Copyright 2023 © The Deno Windowing Team +Copyright 2024 © The Deno Windowing Team diff --git a/deno.jsonc b/deno.jsonc index 8e6e398..977a103 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -17,7 +17,8 @@ "example:window": "deno run -A --unstable examples/window.ts", "example:capture": "deno run -A --unstable examples/mouse-capture.ts", "example:custom_cursor": "deno run -A --unstable examples/custom-cursor.ts", - "example:custom_icon": "deno run -A --unstable examples/custom-icon.ts" + "example:custom_icon": "deno run -A --unstable examples/custom-icon.ts", + "example:webgpu": "deno run -A --unstable examples/webgpu.ts" }, "lint": { diff --git a/examples/clock/window.ts b/examples/clock/window.ts index fae0144..8a7d78a 100644 --- a/examples/clock/window.ts +++ b/examples/clock/window.ts @@ -3,7 +3,7 @@ import { Canvas, CanvasRenderingContext2D, createCanvas, -} from "https://deno.land/x/skia_canvas@0.5.4/mod.ts"; +} from "https://deno.land/x/skia_canvas@0.5.5/mod.ts"; import { createWindow, @@ -12,7 +12,7 @@ import { WindowClosedEvent, WindowFramebufferSizeEvent, WindowRefreshEvent, -} from "https://deno.land/x/dwm@0.3.3/mod.ts"; +} from "../../mod.ts"; export class WindowCanvas { canvas: Canvas; @@ -84,5 +84,5 @@ export class WindowCanvas { } } -export * from "https://deno.land/x/skia_canvas@0.5.4/mod.ts"; -export { mainloop } from "https://deno.land/x/dwm@0.3.3/mod.ts"; +export * from "https://deno.land/x/skia_canvas@0.5.5/mod.ts"; +export { mainloop } from "../../mod.ts"; diff --git a/examples/imgui.ts b/examples/imgui.ts index b05132a..958463f 100644 --- a/examples/imgui.ts +++ b/examples/imgui.ts @@ -1,6 +1,6 @@ -import { createWindow, getProcAddress, mainloop, pollEvents } from "../mod.ts"; +import { createWindow, getProcAddress, mainloop } from "../mod.ts"; import * as gl from "https://deno.land/x/gluten@0.1.3/api/gles23.2.ts"; -import { CBool, createContext, destroyContext, imgui } from "../ext/imgui.ts"; +import { Bool, createContext, destroyContext, imgui } from "../ext/imgui.ts"; const window = createWindow({ title: "Imgui", @@ -19,17 +19,17 @@ addEventListener("close", (event) => { const imguiContext = createContext(window); -const showDemo = new CBool(true); +const showDemo = new Bool(true); await mainloop(() => { gl.Clear(gl.COLOR_BUFFER_BIT); imgui.implOpenGL3NewFrame(); imgui.implGlfwNewFrame(); imgui.newFrame(); imgui.begin("control"); - imgui.checkbox("show demo window", showDemo); + imgui.checkbox("show demo window", showDemo.buffer); imgui.end(); if (showDemo.value) { - imgui.showDemoWindow(showDemo); + imgui.showDemoWindow(showDemo.buffer); } imgui.render(); const drawData = imgui.getDrawData(); diff --git a/examples/imgui2.ts b/examples/imgui2.ts index 6fdb710..f9ee1aa 100644 --- a/examples/imgui2.ts +++ b/examples/imgui2.ts @@ -6,7 +6,6 @@ import { mainloop, WindowCanvas, } from "../ext/canvas.ts"; -import { pollEvents } from "../src/platform/glfw/window.ts"; const win = new WindowCanvas({ title: "IMGUI Skia", diff --git a/examples/webgpu.ts b/examples/webgpu.ts new file mode 100644 index 0000000..8b8ba3a --- /dev/null +++ b/examples/webgpu.ts @@ -0,0 +1,157 @@ +import { createWindow, mainloop } from "../mod.ts"; + +const adapter = await navigator.gpu.requestAdapter(); +const device = await adapter!.requestDevice(); + +const window = createWindow({ + title: "Deno Window Manager", + width: 512, + height: 512, + resizable: true, +}); + +const { width, height } = window.framebufferSize; + +const surface = window.windowSurface(); + +const context = surface.getContext("webgpu"); + +let pipeline: GPURenderPipeline; + +const uniformLength = 5; + +let uniformValues: Float32Array, + uniformBindGroup: GPUBindGroup, + uniformBuffer: GPUBuffer; + +function createPipeline() { + const fragEntry = "fs_main"; + + const shaderModule = device.createShaderModule({ + code: ` + struct Uniforms { + mouse: vec2f, + clicked: f32, + frame: f32, + }; + + @group(0) @binding(0) var shaderplay: Uniforms; + $struct VertexInput { + @builtin(vertex_index) vertex_index: u32, + }; + + @vertex + fn vs_main(in: VertexInput) -> @builtin(position) vec4 { + let x = f32(i32(in.vertex_index) - 1); + let y = f32(i32(in.vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); + } + + @fragment + fn fs_main(@builtin(position) pos: vec4) -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); + } + `, + label: "example", + }); + + const bindGroupLayout = device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, + buffer: { + type: "uniform", + }, + }, + ], + }); + pipeline = device.createRenderPipeline({ + // "auto" layout not working in Deno but works in browser + layout: device.createPipelineLayout({ + bindGroupLayouts: [ + bindGroupLayout, + ], + }), + vertex: { + module: shaderModule, + entryPoint: "vs_main", + buffers: [], + }, + fragment: { + module: shaderModule, + entryPoint: fragEntry, + targets: [ + { + format: "bgra8unorm", + }, + ], + }, + }); + + const value = new Float32Array(uniformLength); + uniformBuffer = device.createBuffer({ + size: value.byteLength, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + uniformValues = value; + + device.queue.writeBuffer(uniformBuffer, 0, value); + + uniformBindGroup = device.createBindGroup({ + layout: bindGroupLayout, + entries: [ + { binding: 0, resource: { buffer: uniformBuffer } }, + ], + }); + + // window.raise(); +} + +createPipeline(); + +context.configure({ + device, + format: "bgra8unorm", +}); + +addEventListener("mousemove", (evt) => { + uniformValues[0] = evt.clientX / width; + uniformValues[1] = evt.clientY / height; +}); + +addEventListener("mousedown", (evt) => { + uniformValues[2] = 1; +}); + +addEventListener("mouseup", (evt) => { + uniformValues[2] = 0; +}); + +await mainloop(() => { + uniformValues[3]++; // frame++ + + const commandEncoder = device.createCommandEncoder(); + const textureView = context.getCurrentTexture().createView(); + + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: textureView, + clearValue: { r: 0, g: 0, b: 0, a: 1 }, + loadOp: "clear", + storeOp: "store", + }, + ], + }); + + device.queue.writeBuffer(uniformBuffer, 0, uniformValues); + + renderPass.setPipeline(pipeline); + renderPass.setBindGroup(0, uniformBindGroup); + renderPass.draw(3, 1); + renderPass.end(); + + device.queue.submit([commandEncoder.finish()]); + surface.present(); +}, false); diff --git a/ext/canvas.ts b/ext/canvas.ts index 1b690ed..863ec73 100644 --- a/ext/canvas.ts +++ b/ext/canvas.ts @@ -2,7 +2,7 @@ import { Canvas, CanvasRenderingContext2D, createCanvas, -} from "https://deno.land/x/skia_canvas@0.5.2/mod.ts"; +} from "https://deno.land/x/skia_canvas@0.5.5/mod.ts"; import { createWindow, @@ -86,4 +86,4 @@ export class WindowCanvas { export * from "../mod.ts"; // deno-lint-ignore ban-ts-comment // @ts-expect-error -export * from "https://deno.land/x/skia_canvas@0.5.2/mod.ts"; +export * from "https://deno.land/x/skia_canvas@0.5.5/mod.ts"; diff --git a/ext/imgui.ts b/ext/imgui.ts index 7fa3efd..3916197 100644 --- a/ext/imgui.ts +++ b/ext/imgui.ts @@ -1,7 +1,7 @@ // deno-lint-ignore-file no-explicit-any import { DwmWindow } from "../mod.ts"; -import * as imgui from "https://raw.githubusercontent.com/djfos/dimgui/main/src/call.ts"; -export * from "https://raw.githubusercontent.com/djfos/dimgui/main/src/type.ts"; +import * as imgui from "https://raw.githubusercontent.com/deno-windowing/dimgui/main/src/call.ts"; +export * from "https://raw.githubusercontent.com/deno-windowing/dimgui/main/src/type.ts"; export function createContext(window: DwmWindow) { const imguiContext = imgui.createContext(); diff --git a/ext/styles/windows.ts b/ext/styles/windows.ts index 7818621..aa4e64c 100644 --- a/ext/styles/windows.ts +++ b/ext/styles/windows.ts @@ -6,19 +6,12 @@ import { DwmEnableBlurBehindWindow, DwmSetWindowAttribute, DWMWA_USE_IMMERSIVE_DARK_MODE, -} from "https://win32.deno.dev/0.2.0/Graphics.Dwm"; +} from "https://win32.deno.dev/0.4.1/Graphics.Dwm"; import { DwmWindow } from "../../mod.ts"; -// const { SetWindowCompositionAttribute } = Deno.dlopen("user32.dll", { -// "SetWindowCompositionAttribute": { -// parameters: ["pointer", "pointer"], -// result: "pointer", -// }, -// }).symbols; - function applyBlur(win: DwmWindow) { DwmEnableBlurBehindWindow( - ffi.glfwGetWin32Window(win.nativeHandle), + ffi.glfwGetWin32Window!(win.nativeHandle), allocDWM_BLURBEHIND({ dwFlags: DWM_BB_ENABLE, fEnable: true, @@ -30,7 +23,7 @@ function applyBlur(win: DwmWindow) { function clearBlur(win: DwmWindow) { DwmEnableBlurBehindWindow( - ffi.glfwGetWin32Window(win.nativeHandle), + ffi.glfwGetWin32Window!(win.nativeHandle), allocDWM_BLURBEHIND({ dwFlags: DWM_BB_ENABLE, fEnable: false, @@ -42,7 +35,7 @@ function clearBlur(win: DwmWindow) { function applyDark(win: DwmWindow) { DwmSetWindowAttribute( - ffi.glfwGetWin32Window(win.nativeHandle), + ffi.glfwGetWin32Window!(win.nativeHandle), DWMWA_USE_IMMERSIVE_DARK_MODE, Uint8Array.of(2), 4, @@ -51,7 +44,7 @@ function applyDark(win: DwmWindow) { function applyLight(win: DwmWindow) { DwmSetWindowAttribute( - ffi.glfwGetWin32Window(win.nativeHandle), + ffi.glfwGetWin32Window!(win.nativeHandle), DWMWA_USE_IMMERSIVE_DARK_MODE, Uint8Array.of(0), 4, @@ -60,7 +53,7 @@ function applyLight(win: DwmWindow) { function applyMica(win: DwmWindow) { DwmSetWindowAttribute( - ffi.glfwGetWin32Window(win.nativeHandle), + ffi.glfwGetWin32Window!(win.nativeHandle), 38, Uint8Array.of(2), 4, @@ -69,7 +62,7 @@ function applyMica(win: DwmWindow) { function applyMicaAlt(win: DwmWindow) { DwmSetWindowAttribute( - ffi.glfwGetWin32Window(win.nativeHandle), + ffi.glfwGetWin32Window!(win.nativeHandle), 38, Uint8Array.of(4), 4, @@ -78,7 +71,7 @@ function applyMicaAlt(win: DwmWindow) { function clearMica(win: DwmWindow) { DwmSetWindowAttribute( - ffi.glfwGetWin32Window(win.nativeHandle), + ffi.glfwGetWin32Window!(win.nativeHandle), 38, Uint8Array.of(0), 4, diff --git a/src/core/event.ts b/src/core/event.ts index 8e6f648..565edc2 100644 --- a/src/core/event.ts +++ b/src/core/event.ts @@ -243,27 +243,8 @@ export class WindowDropEvent extends WindowEvent { export type AnimationFrameCallback = (time: number) => unknown; export const animationFrames = new Map(); -let animationFrameId = 0; - -export function requestAnimationFrameImpl(callback: AnimationFrameCallback) { - animationFrameId++; - animationFrames.set(animationFrameId, callback); - return animationFrameId; -} - -export function cancelAnimationFrameImpl(id: number) { - animationFrames.delete(id); -} - -Object.assign(window, { - requestAnimationFrame: requestAnimationFrameImpl, - cancelAnimationFrame: cancelAnimationFrameImpl, -}); declare global { - const requestAnimationFrame: typeof requestAnimationFrameImpl; - const cancelAnimationFrame: typeof cancelAnimationFrameImpl; - interface WindowEventMap { close: WindowCloseEvent; closed: WindowClosedEvent; diff --git a/src/core/mod.ts b/src/core/mod.ts index 244bb0b..3eac443 100644 --- a/src/core/mod.ts +++ b/src/core/mod.ts @@ -2,3 +2,5 @@ export * from "./common.ts"; export * from "./event.ts"; export * from "./platform.ts"; export * from "./window.ts"; + +export type RawPlatform = "cocoa" | "win32" | "x11"; diff --git a/src/core/window.ts b/src/core/window.ts index 8f0310e..cb95c4b 100644 --- a/src/core/window.ts +++ b/src/core/window.ts @@ -1,4 +1,5 @@ import { ImageStruct, LTRB, Position, Size } from "./common.ts"; +import { RawPlatform } from "./mod.ts"; import { DwmMonitor } from "./monitor.ts"; export interface CreateWindowOptions { @@ -313,6 +314,14 @@ export abstract class DwmWindow { image: ImageStruct, ): void; + /** + * Creates a Window Surface for Use with WebGPU + * ```ts + * const surface = win.windowSurface(); + * const context = surface.getContext("webgpu"); + * ``` + */ + abstract windowSurface(): Deno.UnsafeWindowSurface; /** * Check if the window is closed */ diff --git a/src/platform/glfw/ffi.ts b/src/platform/glfw/ffi.ts index f27e15a..9479425 100644 --- a/src/platform/glfw/ffi.ts +++ b/src/platform/glfw/ffi.ts @@ -1,8 +1,8 @@ import { cachedir } from "https://deno.land/x/cache@0.2.13/directories.ts"; -import darwin from "https://glfw-binaries.deno.dev/3.4.0-patch2/glfw3_darwin.js"; -import darwinAarch64 from "https://glfw-binaries.deno.dev/3.4.0-patch2/glfw3_darwin_aarch64.js"; -import windows from "https://glfw-binaries.deno.dev/3.4.0-patch2/glfw3_windows.js"; -import linux from "https://glfw-binaries.deno.dev/3.4.0-patch2/glfw3_linux.js"; +import darwin from "https://glfw-binaries.deno.dev/3.4.0-patch3/glfw3_darwin.js"; +import darwinAarch64 from "https://glfw-binaries.deno.dev/3.4.0-patch3/glfw3_darwin_aarch64.js"; +import windows from "https://glfw-binaries.deno.dev/3.4.0-patch3/glfw3_windows.js"; +import linux from "https://glfw-binaries.deno.dev/3.4.0-patch3/glfw3_linux.js"; export const GLFW_VERSION = "3.4.0"; @@ -281,6 +281,7 @@ const symbols = { glfwGetWGLContext: { parameters: ["pointer"], result: "pointer", + optional: true, }, glfwGetCocoaMonitor: { parameters: ["pointer"], @@ -472,3 +473,10 @@ export const ffi = mod.symbols; export function cstr(str: string) { return new TextEncoder().encode(str + "\0"); } + +// const objc = Deno.build.os === "darwin" ? Deno.dlopen("libobjc.dylib", { +// objc_getClass: { +// parameters: ["buffer"], +// result: "pointer", +// }, +// } as const).symbols : undefined; diff --git a/src/platform/glfw/window.ts b/src/platform/glfw/window.ts index f253d3a..c4aacca 100644 --- a/src/platform/glfw/window.ts +++ b/src/platform/glfw/window.ts @@ -17,6 +17,7 @@ import { WindowResizeEvent, WindowScrollEvent, } from "../../core/event.ts"; +import { RawPlatform } from "../../core/mod.ts"; import { DwmMonitor } from "../../core/monitor.ts"; import { CreateWindowOptions, @@ -61,7 +62,7 @@ import { } from "./constants.ts"; import { cstr, ffi } from "./ffi.ts"; import { MonitorGlfw } from "./monitor.ts"; -import SCANCODE_WIN from "./scancode_win.json" assert { type: "json" }; +import SCANCODE_WIN from "./scancode_win.json" with { type: "json" }; const { glfwInit, @@ -601,6 +602,20 @@ const dropCallback = new Deno.UnsafeCallback( }, ); +const objc = Deno.build.os === "darwin" + ? Deno.dlopen("libobjc.dylib", { + objc_msgSend_contentView: { + parameters: ["pointer", "pointer"], + result: "pointer", + name: "objc_msgSend", + }, + sel_registerName: { + parameters: ["buffer"], + result: "pointer", + }, + }).symbols + : undefined; + export class WindowGlfw extends DwmWindow { #nativeHandle: Deno.PointerValue; #title = ""; @@ -1054,6 +1069,61 @@ export class WindowGlfw extends DwmWindow { glfwSetCursorPos(this.#nativeHandle, x, y); } + #rawHandle(): [ + RawPlatform, + Deno.UnsafePointerView, + Deno.UnsafePointerView | null, + ] { + let platform: RawPlatform; + let handle: Deno.PointerValue; + let display: Deno.PointerValue; + switch (Deno.build.os) { + case "darwin": + platform = "cocoa"; + handle = ffi.glfwGetCocoaWindow!(this.#nativeHandle); + display = objc!.objc_msgSend_contentView( + handle, + objc!.sel_registerName(cstr("contentView")), + ); + break; + case "windows": { + platform = "win32"; + const kernel32 = Deno.dlopen("kernel32.dll", { + GetModuleHandleW: { + parameters: ["pointer"], + result: "pointer", + }, + }); + handle = ffi.glfwGetWin32Window!(this.#nativeHandle); + display = kernel32.symbols.GetModuleHandleW(null); + kernel32.close(); + break; + } + case "linux": + case "aix": + case "freebsd": + case "illumos": + case "netbsd": + case "solaris": + platform = "x11"; + handle = ffi.glfwGetX11Window!(this.#nativeHandle); + display = ffi.glfwGetX11Display!(); + break; + default: + throw new Error(`Unsupported platform: ${Deno.build.os}`); + } + return [ + platform, + new Deno.UnsafePointerView(handle!), + display == null ? null : new Deno.UnsafePointerView(display), + ]; + } + + windowSurface() { + const [platform, handle, display] = this.#rawHandle(); + return new Deno.UnsafeWindowSurface(platform, handle, display); + } + close() { this.#closed = true; dispatchEvent(new WindowClosedEvent(this));