Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions native/linux/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 79 additions & 1 deletion native/linux/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ mod x11_impl {
static mut WARP_CENTER_X: i32 = 0;
static mut WARP_CENTER_Y: i32 = 0;
static mut RELATIVE_MODE: bool = false;
/// WM_PROTOCOLS / WM_DELETE_WINDOW, interned once in `create_window`.
/// Cached because `poll_events` compares against them every event.
static mut WM_PROTOCOLS: x11::xlib::Atom = 0;
static mut WM_DELETE_WINDOW: x11::xlib::Atom = 0;

pub fn wm_protocols_atom() -> x11::xlib::Atom { unsafe { WM_PROTOCOLS } }
pub fn wm_delete_window_atom() -> x11::xlib::Atom { unsafe { WM_DELETE_WINDOW } }

pub fn set_fullscreen(fullscreen: bool) {
unsafe {
Expand Down Expand Up @@ -184,6 +191,20 @@ mod x11_impl {
x11::xlib::ButtonPressMask | x11::xlib::ButtonReleaseMask |
x11::xlib::PointerMotionMask | x11::xlib::StructureNotifyMask);

// Opt into the WM close protocol before mapping, so the titlebar
// ✕ / Alt-F4 arrives as a ClientMessage we can turn into
// `windowShouldClose()` instead of the WM dropping our X
// connection and Xlib exit(1)-ing the game mid-frame.
WM_PROTOCOLS = x11::xlib::XInternAtom(
DISPLAY, b"WM_PROTOCOLS\0".as_ptr() as *const _, 0);
WM_DELETE_WINDOW = x11::xlib::XInternAtom(
DISPLAY, b"WM_DELETE_WINDOW\0".as_ptr() as *const _, 0);
if WM_DELETE_WINDOW != 0 {
let mut protocols = [WM_DELETE_WINDOW];
x11::xlib::XSetWMProtocols(
DISPLAY, X11_WINDOW, protocols.as_mut_ptr(), 1);
}

if !headless {
x11::xlib::XMapWindow(DISPLAY, X11_WINDOW);
}
Expand Down Expand Up @@ -489,6 +510,23 @@ mod x11_impl {
}
}
}
x11::xlib::ClientMessage => {
// The WM asking us to close (titlebar ✕, Alt-F4, or a
// session logout). Without WM_DELETE_WINDOW in our
// WM_PROTOCOLS the WM instead destroys the connection,
// and Xlib's default IO-error handler calls exit(1)
// from under the game — `windowShouldClose()` never
// goes true, so `closeWindow()` and every shutdown
// path after it (audio teardown, save-on-exit) are
// skipped. Observed as:
// XIO: fatal IO error 62 (Timer expired)
if event.client_message.message_type == wm_protocols_atom()
&& event.client_message.data.get_long(0)
== wm_delete_window_atom() as i64
{
engine().should_close = true;
}
}
x11::xlib::DestroyNotify => {
engine().should_close = true;
}
Expand Down Expand Up @@ -572,6 +610,14 @@ pub extern "C" fn bloom_init_window(width: f64, height: f64, title_ptr: *const u
if !force_sw_gi && supported.contains(rt_mask) {
required_features |= rt_mask;
}
// PT-2: texture binding array + non-uniform indexing for textured
// path-trace hit shading. Both or neither (the kernel indexes the
// array with a per-thread material id).
let pt_tex_mask = wgpu::Features::TEXTURE_BINDING_ARRAY
| wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING;
if supported.contains(pt_tex_mask) {
required_features |= pt_tex_mask;
}
let experimental_features = if required_features.intersects(rt_mask) {
unsafe { wgpu::ExperimentalFeatures::enabled() }
} else {
Expand All @@ -582,6 +628,34 @@ pub extern "C" fn bloom_init_window(width: f64, height: f64, title_ptr: *const u
// PerMaterial, PerDraw, SceneInputs). wgpu's default limit is
// 4. Vulkan supports at least 7 here, so 5 is universally safe.
required_limits.max_bind_groups = 5;
// The refractive/translucent material profile binds up to 19
// sampled textures in the fragment stage (5 material maps + env/
// BRDF/3 shadow cascades/env-diffuse + planar reflection + 3 texture
// arrays + the group-4 scene_color/scene_depth/impulse/motion inputs).
// wgpu's default is 16. Raise to whatever the adapter actually
// supports — every real Vulkan/GL GPU exposes ≥128 — so opaque/
// transparent materials are unaffected and refractive ones link.
let adapter_limits = adapter.limits();
required_limits.max_sampled_textures_per_shader_stage = required_limits
.max_sampled_textures_per_shader_stage
.max(adapter_limits.max_sampled_textures_per_shader_stage);
required_limits.max_samplers_per_shader_stage = required_limits
.max_samplers_per_shader_stage
.max(adapter_limits.max_samplers_per_shader_stage);
// PT-2: binding arrays have their own element budget, default 0.
// Take whatever the adapter offers; the renderer checks the
// granted value against its fixed array size before compiling
// the textured kernel variant.
if required_features.contains(pt_tex_mask) {
required_limits.max_binding_array_elements_per_shader_stage =
adapter_limits.max_binding_array_elements_per_shader_stage;
}
// PT-4: the path-trace kernel binds 9 storage buffers (accum +
// moments + reservoir ping-pongs on top of instance/geo data);
// the wgpu default limit is 8.
required_limits.max_storage_buffers_per_shader_stage = required_limits
.max_storage_buffers_per_shader_stage
.max(adapter_limits.max_storage_buffers_per_shader_stage.min(16));
Comment on lines +631 to +658

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching lib.rs:"
fd 'lib\.rs$' . || true

echo
echo "Relevant section in native/linux/src/lib.rs:"
sed -n '600,675p' native/linux/src/lib.rs

echo
echo "Search for required_limits and DeviceDescriptor/Adapter configuration:"
rg -n "required_limits|max_sampled_textures|max_samplers|max_storage_buffers|DeviceDescriptor|request_adapter|request_device" native/linux/src/lib.rs native -g '*.rs' | head -200

Repository: Bloom-Engine/engine

Length of output: 22087


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search for Bloom renderer resource budget constants/usages:"
rg -n "19 sampled|128|PT-2|PT-4|path-trace kernel|9 storage|max_binding_array_elements_per_shader_stage|req.*binding|fixed array size|max_binding_array_elements" native/shared native/linux native/windows native/web | head -200

echo
echo "Check native/windows and shared attach mirroring:"
sed -n '750,802p' native/windows/src/lib.rs
sed -n '155,182p' native/shared/src/attach.rs

Repository: Bloom-Engine/engine

Length of output: 15576


🌐 Web query:

Bloom Engine native linux renderer 19 sampled textures 9 storage buffers path-trace kernel

💡 Result:

The Bloom Engine is a cross-platform graphics engine written in TypeScript that compiles to native binaries (macOS, Windows, Linux, iOS, tvOS) and WASM for the web [1][2]. Its renderer is built on the wgpu API [1][2], which abstracts graphics backends such as Vulkan, Metal, DirectX 12, and WebGPU [1]. Regarding your query about a "19 sampled textures 9 storage buffers path-trace kernel" configuration, there is no public documentation or technical evidence indicating that the Bloom Engine utilizes a specific hardcoded configuration of 19 sampled textures and 9 storage buffers for a path-tracing kernel. The engine's current development focus involves GPU-driven rendering techniques, including indirect multi-draw and GPU culling [3], as well as advanced lighting features like global illumination probes and variance-driven refinement [4][5]. While the engine explores compute-heavy tasks like global illumination feedback loops (WSRC) [4], it explicitly notes that some "exotic" features such as native hardware ray tracing are currently off the table due to the constraints of the wgpu abstraction layer [2]. Technical discussions within the Bloom Engine repository often reference compute shader dispatch and binding patterns [4][5][3], but do not align with the specific numbers mentioned in your query. It is possible that these numbers refer to a custom implementation or a specific project configuration not part of the core Bloom Engine codebase.

Citations:


Reject adapters below Bloom’s fixed resource budgets.

The Linux/Windows path-tracing setup documents hard requirements of 19 sampled textures and 9 storage buffers, but the device request currently downgrades to the adapter’s lower limits. Query the requested limits against these minima before request_device; if an adapter cannot support them, fail initialization clearly or select a fallback renderer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/linux/src/lib.rs` around lines 631 - 658, Update the adapter-limit
handling before request_device to validate Bloom’s fixed minimum budgets: at
least 19 sampled textures and 9 storage buffers per shader stage. Reject
initialization with a clear error or select the existing fallback renderer when
adapter_limits cannot satisfy either minimum; do not lower required_limits to an
unsupported adapter value.

if required_features.intersects(rt_mask) {
required_limits = required_limits
.using_minimum_supported_acceleration_structure_values();
Expand All @@ -603,7 +677,11 @@ pub extern "C" fn bloom_init_window(width: f64, height: f64, title_ptr: *const u
// logical size separately so screenWidth() etc. stay
// DPI-independent.
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
// COPY_SRC: bloom_take_screenshot reads the swapchain back;
// without it the readback copy is a validation error that
// aborts the process the first time a game calls it.
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
Comment on lines +680 to +684

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '1,120p'

echo "== target file size/outline =="
wc -l native/linux/src/lib.rs || true
ast-grep outline native/linux/src/lib.rs --view compact 2>/dev/null | sed -n '1,220p' || true

echo "== relevant lines =="
sed -n '620,730p' native/linux/src/lib.rs | cat -n

Repository: Bloom-Engine/engine

Length of output: 10276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== screenshot and screenshot-related locations =="
rg -n "bloom_take_screenshot|bloom_register_geisterhand_screenshot|COPY_SRC|swapchain|swap_chain|SurfaceCapabilities|surface_caps|configure" native/linux/src/lib.rs native/linux -g '*.rs' || true

echo "== renderer surface usage/configuration context =="
rg -n "SurfaceConfiguration|RENDER_ATTACHMENT|COPY_SRC|bloom_take_screenshot|screen reader|readback|present" --glob '*.rs' .

echo "== likely screenshots section =="
sed -n '1140,1284p' native/linux/src/lib.rs | cat -n

Repository: Bloom-Engine/engine

Length of output: 37222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== windows surface config around COPY_SRC =="
sed -n '790,835p' native/windows/src/lib.rs | cat -n

echo "== web copy-src handling =="
sed -n '160,195p' native/web/src/lib.rs | cat -n

echo "== shared attach =="
sed -n '220,255p' native/shared/src/attach.rs | cat -n

echo "== shared renderer surface config =="
sed -n '1989,2032p' native/shared/src/renderer/mod.rs | cat -n

echo "== screenshot implementation in shared renderer =="
sed -n '11160,11245p' native/shared/src/renderer/mod.rs | cat -n

echo "== wasm screenshot =="
sed -n '334,350p' native/web/src/parity_ffi.rs | cat -n

Repository: Bloom-Engine/engine

Length of output: 13276


🌐 Web query:

wgpu SurfaceCapabilities usages TextureUsages configure surface copy-src documentation

💡 Result:

To use a surface texture as a source for copy operations (e.g., using COPY_SRC), you must ensure that your SurfaceConfiguration includes the appropriate TextureUsages when calling surface.configure. 1. Query Surface Capabilities: Before configuring the surface, retrieve the supported texture usages for your specific adapter by calling surface.get_capabilities(adapter) [1][2]. This returns a SurfaceCapabilities struct, which contains a usages field specifying which TextureUsages are supported by the surface [3][2]. Note that TextureUsages::RENDER_ATTACHMENT is the only usage guaranteed to be supported across all surfaces [3][4][5]. 2. Configure the Surface: When you call surface.configure, you must provide a SurfaceConfiguration [1][4]. The usage field in this struct defines the allowed operations for textures retrieved from the surface via get_current_texture [4][5]. To perform a copy-src operation (such as copy_texture_to_buffer), the SurfaceConfiguration's usage must include TextureUsages::COPY_SRC [6][7]. 3. Important Considerations: - Compatibility: Always check the usages returned by get_capabilities before configuring [2]. If you request a usage not supported by the underlying surface/adapter, the configuration may fail or cause runtime issues [1][8]. - Performance and Driver Behavior: Different backends (Vulkan, DX12, Metal) have varying levels of support for surface usages beyond RENDER_ATTACHMENT [8]. Always ensure the requested flags are subset of the capabilities returned by the surface [2]. - COPY_SRC Usage: TextureUsages::COPY_SRC specifically allows a texture to be used as the source in command encoder operations like copy_texture_to_buffer or copy_texture_to_texture [6][7]. Example flow: let caps = surface.get_capabilities(&adapter); let config = wgpu::SurfaceConfiguration { usage: caps.usages & wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT, format: caps.formats, //... other fields }; surface.configure(&device, &config);

Citations:


Check COPY_SRC support before configuring the Linux swapchain.

surface.configure(...) requires the requested usages to be present in surface_caps.usages; RENDER_ATTACHMENT is not the surface’s only guaranteed usage flag. Configuring with unguarded COPY_SRC can fail window initialization on surfaces that only support render attachment, even though the Linux screenshot path relies on copy_texture_to_buffer of the current swapchain texture. Use RENDER_ATTACHMENT | (surface_caps.usages & TextureUsages::COPY_SRC) as the configured usage, and fail the screenshot path with an unsupported-screenshot error when COPY_SRC is unavailable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/linux/src/lib.rs` around lines 680 - 684, Update the Linux swapchain
configuration to use RENDER_ATTACHMENT combined with only the COPY_SRC bit
supported by surface_caps.usages. In the Linux screenshot path, detect when
COPY_SRC is unavailable and return the existing unsupported-screenshot error
instead of attempting copy_texture_to_buffer.

format,
width: phys_w,
height: phys_h,
Expand Down
24 changes: 11 additions & 13 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,6 @@ export function drawModelTransform(model: Model, m16: number[], tint: Color): vo
);
}

export function unloadModel(model: Model): void {
bloom_unload_model(model.handle);
}

/**
* Return the axis-aligned bounding box of a loaded model in its local
* coordinate space. Computed once at load time from mesh vertex positions.
Expand Down Expand Up @@ -356,15 +352,6 @@ export function compileMaterial(wgslSource: string): number {
export const PROFILE_OPAQUE = 0;
export const PROFILE_TRANSLUCENT = 1;

/// Material bucket — matches `Bucket` in the engine. Decides which
/// pass the draw lands in and what sort order applies. Refractive
/// also triggers a scene-colour snapshot before the pass runs.
export const BUCKET_OPAQUE = 0;
export const BUCKET_TRANSPARENT = 1;
export const BUCKET_REFRACTIVE = 2;
export const BUCKET_ADDITIVE = 3;
export const BUCKET_CUTOUT = 4;

/// Phase 4b — full-control material compile. Pass PROFILE_* and
/// BUCKET_* constants. `readsScene` enables the group-4 SceneInputs
/// binding; required for refraction / shoreline / depth-fade effects.
Expand Down Expand Up @@ -421,6 +408,17 @@ export function compileMaterialInstanced(wgslSource: string): number {

declare function bloom_compile_material_instanced_bucket(src: number, bucket: number, readsScene: number): number;

/// Material bucket — the wire values `bloom_compile_material_instanced_bucket`
/// decodes (see `Renderer::compile_material_instanced_bucket`). These are the
/// FFI's own numbering, NOT the discriminants of the Rust `Bucket` enum, and
/// they only cover the buckets the instanced compile can reach: Refractive has
/// no wire value here — use `compileRefractiveMaterial` for that.
///
/// A second, conflicting BUCKET_* block used to sit further up this file with
/// Phase 4b's numbering (TRANSPARENT=1, REFRACTIVE=2, ADDITIVE=3, CUTOUT=4).
/// Two `export const`s of the same name made Perry emit duplicate LLVM
/// definitions, so clang rejected this whole module and every game importing
/// `bloom/models` failed to link against empty `_perry_init_*` stubs.
export const BUCKET_OPAQUE = 0;
export const BUCKET_CUTOUT = 1;
export const BUCKET_ADDITIVE = 2;
Expand Down
Loading