///|
/// Graphics backend selection contracts.
///
/// Ebiten refs:
/// - internal/graphicsdriver/graphics.go
/// - internal/ui/ui_glfw.go (platform dependent surface binding)
///|
pub(all) enum GraphicsBackendKind {
WgpuNative
WebGpu
WebGl2
Null
} derive(Debug)
///|
pub impl Show for GraphicsBackendKind with fn output(self, logger) {
logger.write_object(to_repr(self))
}
///|
pub(all) struct GraphicsBackendOptions {
enable_validation : Bool
prefer_low_power : Bool
enable_vsync : Bool
} derive(Debug)
///|
pub impl Show for GraphicsBackendOptions with fn output(self, logger) {
logger.write_object(to_repr(self))
}
///|
pub struct StubGraphicsDriver {
backend : GraphicsBackendKind
mut width : Int
mut height : Int
mut initialized : Bool
mut native_active : Bool
mut web_active : Bool
mut next_id : Int
mut begin_count : Int
mut end_count : Int
mut draw_count : Int
mut resize_count : Int
mut resize_suppressed_count : Int
mut last_resize_duration_ms : Double
mut total_resize_duration_ms : Double
} derive(Debug)
///|
pub struct GraphicsResizeStats {
resize_count : Int
suppressed_count : Int
current_width : Int
current_height : Int
last_resize_duration_ms : Double
total_resize_duration_ms : Double
} derive(Debug)
///|
pub impl Show for GraphicsResizeStats with fn output(self, logger) {
logger.write_object(to_repr(self))
}
///|
pub type NativeGraphicsDriver = StubGraphicsDriver
///|
pub type WebGpuGraphicsDriver = StubGraphicsDriver
///|
pub type WebGlGraphicsDriver = StubGraphicsDriver
///|
pub type NullGraphicsDriver = StubGraphicsDriver
///|
/// A factory that constructs a backend driver for a given kind / surface
/// / options triple. Useful when a host wants to defer the choice of
/// (wgpu-native vs WebGPU vs WebGL2 vs Null) until runtime feature
/// detection.
pub(open) trait GraphicsBackendFactory {
/// Build and install a driver for the requested `kind` against the
/// surface. Implementations may raise if `kind` is unsupported or the
/// surface is incompatible.
fn create(
Self,
kind : GraphicsBackendKind,
surface : SurfaceToken,
options : GraphicsBackendOptions,
) -> Unit raise
}
///|
pub fn default_graphics_backend_options() -> GraphicsBackendOptions {
{ enable_validation: true, prefer_low_power: false, enable_vsync: true }
}
///|
fn default_clock_now_ms() -> Double {
0.0
}
///|
let graphics_clock_provider : Ref[() -> Double] = Ref::new(default_clock_now_ms)
///|
pub fn set_graphics_clock_provider(clock : () -> Double) -> Unit {
graphics_clock_provider.val = clock
}
///|
pub fn reset_graphics_clock_provider() -> Unit {
graphics_clock_provider.val = default_clock_now_ms
}
///|
pub fn create_wgpu_native_graphics(
surface : SurfaceToken,
options : GraphicsBackendOptions,
) -> NativeGraphicsDriver {
let _ = options
{
backend: GraphicsBackendKind::WgpuNative,
width: surface.width,
height: surface.height,
initialized: false,
native_active: false,
web_active: false,
next_id: 0,
begin_count: 0,
end_count: 0,
draw_count: 0,
resize_count: 0,
resize_suppressed_count: 0,
last_resize_duration_ms: 0.0,
total_resize_duration_ms: 0.0,
}
}
///|
pub fn create_webgpu_graphics(
surface : SurfaceToken,
options : GraphicsBackendOptions,
) -> WebGpuGraphicsDriver {
let _ = options
{
backend: GraphicsBackendKind::WebGpu,
width: surface.width,
height: surface.height,
initialized: false,
native_active: false,
web_active: false,
next_id: 0,
begin_count: 0,
end_count: 0,
draw_count: 0,
resize_count: 0,
resize_suppressed_count: 0,
last_resize_duration_ms: 0.0,
total_resize_duration_ms: 0.0,
}
}
///|
pub fn create_webgl_graphics(
surface : SurfaceToken,
options : GraphicsBackendOptions,
) -> WebGlGraphicsDriver {
let _ = options
{
backend: GraphicsBackendKind::WebGl2,
width: surface.width,
height: surface.height,
initialized: false,
native_active: false,
web_active: false,
next_id: 0,
begin_count: 0,
end_count: 0,
draw_count: 0,
resize_count: 0,
resize_suppressed_count: 0,
last_resize_duration_ms: 0.0,
total_resize_duration_ms: 0.0,
}
}
///|
pub fn create_null_graphics(width : Int, height : Int) -> NullGraphicsDriver {
{
backend: GraphicsBackendKind::Null,
width,
height,
initialized: false,
native_active: false,
web_active: false,
next_id: 0,
begin_count: 0,
end_count: 0,
draw_count: 0,
resize_count: 0,
resize_suppressed_count: 0,
last_resize_duration_ms: 0.0,
total_resize_duration_ms: 0.0,
}
}
///|
pub impl GraphicsDriver for StubGraphicsDriver with fn initialize(self) {
if self.initialized {
return
}
match self.backend {
GraphicsBackendKind::WgpuNative =>
self.native_active = native_try_initialize(self.width, self.height)
GraphicsBackendKind::WebGpu | GraphicsBackendKind::WebGl2 =>
self.web_active = web_graphics_try_initialize(
self.backend,
self.width,
self.height,
)
_ => ()
}
self.initialized = true
}
///|
pub impl GraphicsDriver for StubGraphicsDriver with fn begin(self, pass) {
let _ = self.backend
if self.initialized {
// Re-check web initialization if not yet active (async WebGPU device init)
if !self.web_active {
match self.backend {
GraphicsBackendKind::WebGpu | GraphicsBackendKind::WebGl2 =>
self.web_active = web_graphics_try_initialize(
self.backend,
self.width,
self.height,
)
_ => ()
}
}
self.begin_count = self.begin_count + 1
native_on_begin(self.native_active, pass)
web_graphics_on_begin(self.web_active, self.backend, pass)
}
}
///|
pub impl GraphicsDriver for StubGraphicsDriver with fn end(self, present) {
if self.initialized {
self.end_count = self.end_count + 1
native_on_end(self.native_active, present)
web_graphics_on_end(self.web_active, self.backend, present)
}
}
///|
pub impl GraphicsDriver for StubGraphicsDriver with fn resize(
self,
width,
height,
) {
let next_width = if width <= 0 { 1 } else { width }
let next_height = if height <= 0 { 1 } else { height }
if self.width == next_width && self.height == next_height {
self.resize_suppressed_count = self.resize_suppressed_count + 1
return ()
}
let t0 = (graphics_clock_provider.val)()
self.resize_count = self.resize_count + 1
self.width = next_width
self.height = next_height
if self.initialized {
native_on_resize(self.native_active, next_width, next_height)
web_graphics_on_resize(
self.web_active,
self.backend,
next_width,
next_height,
)
}
let t1 = (graphics_clock_provider.val)()
let duration = t1 - t0
self.last_resize_duration_ms = duration
self.total_resize_duration_ms = self.total_resize_duration_ms + duration
}
///|
/// Snapshot of resize counters and the latest resize duration. Useful
/// for diagnosing flaps where the host requests many no-op resizes.
pub fn StubGraphicsDriver::resize_stats(
self : StubGraphicsDriver,
) -> GraphicsResizeStats {
{
resize_count: self.resize_count,
suppressed_count: self.resize_suppressed_count,
current_width: self.width,
current_height: self.height,
last_resize_duration_ms: self.last_resize_duration_ms,
total_resize_duration_ms: self.total_resize_duration_ms,
}
}
///|
pub impl GraphicsDriver for StubGraphicsDriver with fn new_image(
self,
width,
height,
) {
self.next_id = self.next_id + 1
native_on_new_image(self.native_active, self.next_id, width, height)
{ id: self.next_id, width, height }
}
///|
pub impl GraphicsDriver for StubGraphicsDriver with fn new_shader(self, source) {
self.next_id = self.next_id + 1
{ id: self.next_id, source }
}
///|
pub impl GraphicsDriver for StubGraphicsDriver with fn draw_triangles(
self,
command,
) {
if self.initialized {
self.draw_count = self.draw_count + 1
native_on_draw(self.native_active, command)
web_graphics_on_draw(self.web_active, self.backend, command)
}
}
///|
pub impl GraphicsDriver for StubGraphicsDriver with fn read_pixels(
self,
x,
y,
width,
height,
) {
if !self.initialized {
return None
}
let read_w = if x + width > self.width { self.width - x } else { width }
let read_h = if y + height > self.height { self.height - y } else { height }
if read_w <= 0 || read_h <= 0 {
return None
}
match native_on_read_pixels(self.native_active, x, y, read_w, read_h) {
Some(pixels) => Some(pixels)
None =>
web_graphics_on_read_pixels(
self.web_active,
self.backend,
x,
y,
read_w,
read_h,
)
}
}