- gpu_select takes Vulkan for a window on Windows; gvk_init asks for VK_KHR_surface,
VK_KHR_win32_surface and VK_KHR_swapchain when the renderer will present. A window elsewhere
stays on OpenGL with the reason.
- gvk_open opens the window without a GL context and makes the screen images at its client area;
gvk_swap_make builds the swapchain on the Win32 surface (FIFO with vsync, mailbox or immediate
without) and rebuilds it when it is out of date, suboptimal or the window changes size.
- gvk_present blits the screen image into the acquired swapchain image, flipped (the screen keeps
OpenGL's bottom-up rows), and presents it.
- Samplers pointed at a texture unit with u_i and then fed by binding units - the actors do this -
read that unit's texture; on Vulkan they drew with the white stand-in (the tent, the log, the
chair, the workbench).
- gl.ll carries a weak win_gl_drawable for headless macOS builds.
On the RTX 3070 Ti the windowed build runs the valley through Vulkan at 3840x2160 with the HUD and
the frame-rate readout (16 fps: every upload still waits on the frame, and buffers are
host-visible). The headless Vulkan frame is unchanged; OpenGL frames byte-identical at the five
viewpoints with 59 self-tests passing. The actor fix is compiled and OpenGL-verified, not yet
seen on the PC; blades at the grass's middle distance still draw black on Vulkan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OpenGL links a vertex output to a fragment input by name; SPIR-V links them by location, and
glslang's --auto-map-locations numbered each stage in its own declaration order. The foliage
prepass's depth.frag declares v_wpos then v_uv where model.vert writes v_wpos, v_nrm, v_uv, so
the prepass read a normal as its texture coordinate, its alpha test cut every flower head and
leaf, and the lit pass (depth EQUAL) drew nothing over them. `ludic-dev shaders` now gives both
stages explicit locations: the vertex stage's out order numbers them and the fragment stage looks
each in up by name. All 45 variants checked: every fragment input sits on its vertex output.
Also:
- A clear still waiting for its pass when the framebuffer changes now runs on that framebuffer,
instead of becoming the load op of whichever pass began next.
- R3D_DUMP_ATLAS writes every impostor and card atlas a run bakes (build/atlas_<n>_*.ppm); the
40 baked on Vulkan match OpenGL's.
The PC's Vulkan frame now shows the flowers as OpenGL does, validation-clean. ludic-dev test 140
passed; OpenGL frames byte-identical at the five viewpoints; 59 self-tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things the first Vulkan frames on the RTX 3070 Ti showed against OpenGL on the same PC:
- Exposure: the adaptation pass reads the HDR scene's smallest mip, and a render target made
without pixels had one level, so exposure came from a single texel. A target asked for mipmaps
now grows a full chain (level 0 kept), and passes draw through level-0 views keyed by the
image's generation.
- Foliage: the depth prepass and the lit pass (depth EQUAL) are different variants. Vulkan vertex
stages now declare an invariant gl_Position so both land on the same depth.
- Alpha to coverage is enabled only on a multisampled pass. OpenGL ignores it without MSAA; Vulkan
with one sample dropped every fragment under half alpha.
vk_resources also checks a big-endian 16-bit RGB upload (a normal map). VKRES OK; ludic-dev test
140 passed; the PC's Vulkan frame is validation-clean; OpenGL frames byte-identical at the five
viewpoints with 59 self-tests passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gpu.ludic's calls branch to the Vulkan backend when it was chosen and came up; every OpenGL
statement is unchanged, only guarded. R3D_GFX=vk selects it in a headless run (the window's
swapchain is the next milestone) and falls back to OpenGL, with the reason, when the device or
the SPIR-V manifest is missing.
- Render state is cached as before and turned into pipelines at the draw; u_* and sampler binds
go into the variant's uniform blocks; meshes, buffers and textures are gvk_* objects;
framebuffer binds are dynamic-rendering passes; same-size blits are image copies; the screen,
the photograph read-back, the present and the screenshot go through the frame.
- Work that submits on its own (uploads, read-backs, new or freed images and buffers) flushes the
frame first, so it runs in OpenGL's order. A read may take fewer channels than the image has
(the height field's R from its RGBA32F bake). Pipeline keys name vertex bindings by order, not
buffer handle, so re-pointed instance buffers keep their pipeline.
The valley renders at frame 90 validation-clean on the RTX 3070 Ti and on MoltenVK. OpenGL frames
byte-identical at the five viewpoints; 59 self-tests pass with no GL error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The drawing half of the Vulkan backend, compiled into render3d and not yet reached from it:
- gvk_program: a program handle as its manifest variant - two SPIR-V modules, a descriptor set
layout (the vertex block at 0, the fragment block at 1, the samplers at their manifest bindings)
and a pipeline layout. Nothing is compiled at run time.
- gvk_pipeline: one pipeline per program, recorded vertex layout, render state and pass formats,
built the first time that combination draws. Attributes the shader does not read are left out;
the front face is clockwise, since neither API flips y between clip space and its target rows.
- gvk_uniform / gvk_u_set / gvk_bind_texture: loose uniforms written into each stage's block at
the manifest's offsets and array strides; gvk_draw_set copies the blocks into a per-frame ring
at the device's alignment and fills a descriptor set from a per-frame pool, with a white 1x1
texture for a sampler nothing was bound to.
- The frame: one command buffer; a framebuffer bind ends the pass and the next begins at its first
clear or draw (a clear that comes first is the load op); attachments move to attachment layouts
for the pass and back to SHADER_READ_ONLY after it, a cascade drawn through a view of its layer.
gvk_present submits and waits; gvk_screenshot reads the screen image back bottom row first.
r3d.ludic now imports gpu_manifest.ludic too. Textures remember their size.
OpenGL frames byte-identical at the five viewpoints; 59 self-tests pass; VKRES OK and VKDEVICE OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The renderer's projections are OpenGL's, whose clip-space depth runs from -w to w; Vulkan clips
everything below 0. `ludic-dev shaders` now wraps each vertex stage - its own main runs, then
gl_Position.z = (z + w) / 2 - so every variant's depth lands in [0, w]. The GLSL the OpenGL
renderer compiles is untouched. No y flip is needed: a Vulkan target's row 0 is where OpenGL's is
(NDC y = -1), so render to texture, sampling and gl_FragCoord agree between the two, and only the
present and the screenshot flip.
45 vertex modules regenerated (spirv-val clean, manifest unchanged); ludic-dev test 140 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gpu_vk_res.ludic gains what gpu.ludic's texture and buffer handles stand for on Vulkan:
- gvk_tex_storage / _upload / _mips / _read / _release: an image and view per handle, a full mip
chain when pixels come with it (the renderer asks for mipmaps after the upload) and one level
for a target, every level in SHADER_READ_ONLY between uses. Uploads are converted to what the
image stores: a missing alpha filled opaque (three-channel formats are stored with four), 16-bit
PNG samples byte-swapped when the unpack state says so, 32-bit float HDR halved into half
floats. Mips are blitted down level by level; a read-back brings level 0 home.
- gvk_sampler: one VkSampler per filter / wrap / compare / anisotropy combination, made when first
asked for, with GL's defaults where the renderer set nothing.
- gvk_buf_*: vertex, index and instance buffers behind one handle, kept when an upload fits.
Host-visible while the backend comes up.
gpu_vk.ludic switches on anisotropic sampling where the device has it and reads its limit.
examples/rendering/vk_resources.ludic checks it all: VKRES OK, validation-clean, every allocation
freed, on the RTX 3070 Ti (16x anisotropy) and on MoltenVK. One run on the Mac crashed while a
headless game run was using the GPU and did not come back in two reruns. OpenGL frames
byte-identical at the five viewpoints; 59 self-tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Vulkan backend is two files. gpu_vk.ludic is the device, memory and one-shot commands,
pure Vulkan, and still runs on its own (vk_device.ludic). gpu_vk_res.ludic is the resources in
the renderer's vocabulary - OpenGL's names for formats, filters and blend factors, which only
exist where Gl.* is named - starting with the format table. r3d.ludic imports both after
gpu.ludic; nothing calls them yet.
OpenGL frames byte-identical at the five viewpoints; 59 self-tests pass; VKDEVICE OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first part of the Vulkan side of gpu.ludic, not yet reached from the renderer: gvk_init
brings up the instance (portability enumeration where offered), the first discrete GPU, a
graphics queue and a device with the Tier 1 floor switched on (dynamic rendering,
synchronization2, descriptor indexing, timeline semaphores), and says why when it cannot so
the caller stays on OpenGL. gvk_mem_type / gvk_alloc pick and allocate memory (one allocation
per resource while the backend comes up), and gvk_once_begin / gvk_once_end carry uploads,
bakes and read-backs through submit and wait.
examples/rendering/vk_device.ludic runs it alone: VKDEVICE OK and validation-clean on the RTX
3070 Ti and on MoltenVK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A game ended its frame with Gl.swap() and shot it with Gl.screenshot, which tied it to OpenGL
and made it name Gl.* only so the parser would splice the GL runtime. gpu_present and
gpu_screenshot carry both (and name Gl.* themselves, so importing render3d is enough), and
r3d_present / r3d_screenshot are what a game calls. The Vulkan backend takes both over.
OpenGL frames byte-identical at the five viewpoints; 59 self-tests pass with no GL error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Slang vertex and fragment shader draw a triangle into an image with dynamic rendering (no
render pass object: the pipeline names its colour format), image barriers move it to the
attachment and transfer layouts, and vkCmdCopyImageToBuffer reads it back into a PPM. It is
the path the Vulkan renderer's passes and screenshots take.
The corner comes from SV_VulkanVertexID: Slang compiles SV_VertexID to gl_VertexIndex -
gl_BaseVertex, which declares the draw-parameters capability.
Validation-clean and VKTRIANGLE OK on the RTX 3070 Ti (Windows) and the M4 Pro (MoltenVK),
13824 pixels covered on both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last of milestone 1: no OpenGL call is left in render3d outside gpu.ludic but the clock
and the CPU-side buffer helpers.
- gpu_program builds a program and remembers the variant it came from (vertex, fragment and
defines as one line - the SPIR-V manifest's key), so a backend that cannot compile at run
time finds the pipeline for the same handle. gpu_use_program and gpu_program_free replace
32 uses and 2 frees; the overlay's program is recorded under overlay.vert|overlay.frag.
- gpu_query_new / _begin / _end / _result carry R3D_PROF's timers.
- gpu_open, gpu_vsync, gpu_renderer_name and gpu_resize_check carry the context.
- r3d_program_tess is gone: nothing called it and no tessellation shader exists.
- R3D_GLCHECK also checks after framebuffer and renderbuffer changes, viewport, draw buffers,
program use, texture parameters, the resize check and between frames.
OpenGL frames are byte-identical at the five viewpoints; 59 self-tests pass with and without
R3D_GLCHECK, with no GL error; ludic-dev test 140 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The overlay flushed into whatever framebuffer was bound last, which raised GL error 1286 on
every run: ov_flush now binds the screen and its viewport before it draws.
R3D_GLCHECK=1 checks each draw, clear, blit, upload and attachment for a pending error or an
incomplete framebuffer and names the target, and each sampler bind for a texture with no image
or a mipmap filter without mipmaps. Off, it costs one flag test.
Still open: an intermittent 1286 reported at "terrain shadow bake" after the self-tests (about
half the runs), and one macOS "unloadable" texture warning at shutdown while the post targets are
freed. No frame samples a bad texture.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
win_native_window / win_native_instance hand a Vulkan swapchain what it is created on: the
HWND and module instance on Windows (VkWin32SurfaceCreateInfoKHR), the game's view on macOS.
Headless builds define both as null (weak on macOS, so a windowed link keeps cocoa.ll's), so
code that asks for them links everywhere and simply finds no window.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gpu_manifest.ludic loads shaders/spv/manifest.txt and answers what a backend asks while drawing:
which variant a program built by r3d_program is, where a uniform lives in its stage's block,
which binding a sampler has, what the vertex inputs are. examples/rendering/vk_manifest.ludic
resolves every program in variants.list against it (45 of 45) and checks a few offsets and
bindings. Nothing imports it yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Framebuffers and their attachments, renderbuffers (multisampled too), draw and read buffers,
completeness checks, blits, viewports, clears, the screen framebuffer, the multisample enable,
the wireframe switch and GL error checks now go through gpu_fb_* / gpu_rb_* / gpu_viewport /
gpu_clear / gpu_blit / gpu_check, and no other file names them. Each call is the one GL call it
replaces, in the same order: the fixed viewpoints render bit-identically and the game's
self-tests report exactly what they did.
What is attached to each framebuffer - colour slots, a depth texture or one layer of an array,
renderbuffers and their samples - is recorded as it is attached, for a backend that builds
render passes and image views. gpu_read_screen is the frame read-back a photograph takes.
R3D_GLCHECK=1 checks, around every draw, clear and blit, that the bound framebuffer is complete
and that no error is left behind, naming the framebuffer. It has already narrowed the old
"gl error 1286 at terrain shadow bake": the error is pending before a draw after the map self-
test, so it comes from a call that is not a draw, clear or blit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Texture creation, 2D and array uploads, pixel packing, filters, wraps, comparison, border,
anisotropy, mipmaps, texture units, read-backs and freeing now go through gpu_tex_* and
gpu_bind_sampler, and no other file names them. Each call is the one GL call it replaces,
in the same order, so OpenGL renders bit-identically at the fixed viewpoints and the game's
self-tests report what they did before.
What those calls say about a texture - size, format, layers, min and mag filter, wraps,
comparison, mipmaps, anisotropy - is recorded per handle as it is set: a backend with
immutable images and separate sampler objects creates both from exactly that.
r3d_bind_tex takes a texture kind (GPU_TEX2D / GPU_TEX2D_ARRAY) instead of a GL target.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every vertex array, vertex and index buffer, attribute pointer, instance divisor, stream
upload and draw call now goes through gpu_mesh_* / gpu_buffer_* / gpu_draw_*, and no other
file in the package names them. A Mesh records its layout as it is built - which buffer
feeds which attribute at what stride and offset, per vertex or per instance - so a backend
that bakes vertex input into a pipeline can read it back. On OpenGL each call is the GL it
replaces, in the same order: the five fixed viewpoints render bit-identically and the game's
self-tests report exactly what they did before.
scatter_attach takes the mesh rather than its vertex array; a mesh now frees every vertex
buffer it owns (glTF meshes used to keep all but the first); the two helpers nothing called,
mesh_grid_patches and mesh_instance_buffer, are gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vulkan cannot compile GLSL when the game starts, so the programs render3d builds are listed
(shaders/variants.list, 45 of them, collected with R3D_PROGRAMS_LOG across the self-tests, the
screens, the viewpoints and the debug switches) and `ludic-dev shaders` compiles each into
shaders/spv/<id>.vert.spv and .frag.spv with a manifest of what the backend needs: each
stage's uniform block and member offsets, the samplers' bindings, the vertex inputs.
The GLSL is the renderer's own, assembled as programs.ludic assembles it, through glslang's
relaxed Vulkan mode, so gpu_uniform / u_* can write the same uniforms into a block on Vulkan.
Bindings are assigned by the tool (glslang's own numbering put several samplers of one stage
on binding 0): the vertex block is 0, the fragment block 1, samplers from 2 in name order,
shared across both stages. Every stage passes spirv-val; `ludic-dev test` rebuilds and compares
wherever the Vulkan SDK is installed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ls a b` fails when `a` is missing even though `b` exists, so with VULKAN_SDK unset the
guard skipped the check on a Mac that has the SDK under ~/VulkanSDK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Render state (depth, blending, culling, colour writes, alpha-to-coverage, depth bias,
scissor) and uniforms go through gpu_* / u_* and nowhere else; OpenGL state is cached and
only changes reach the driver. Frames are bit-identical to before at the fixed viewpoints.
R3D_GFX=gl|vk or gpu_request chooses a backend, falling back to OpenGL with a reason.
gpu_caps_probe() asks Vulkan, on Windows, for the 1.3 floor, ray tracing, mesh shaders, the
NVIDIA RTX generation, Reflex and HDR colour spaces, for a game's settings to grey out what
a machine cannot use; R3D_CAPS pretends to be a given card for tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ludic-dev vkgen reads vk.xml into runtime/native/vk_api.ludic (constants, every struct's
<Struct>_sizeof and <Struct>_<field> offsets, one extern per command) and vk_thunks.ll.
Every size and offset was compiled against the SDK's C headers; `ludic-dev test` checks the
tracked files against the registry wherever the Vulkan SDK is installed.
vk_win.ll (vulkan-1.dll) and vk_mac.ll (libvulkan.1.dylib, MoltenVK) open the loader at run
time, so a program built with Vk.* starts on a machine without Vulkan. ludicc and ludic
build link both for any program that uses Vk.*. The seeds are regenerated for the new
compiler.
vk_probe reports what a machine's Vulkan can do; vk_compute dispatches a Slang compute
shader and reads the picture back, clean under the validation layer on an RTX 3070 Ti and
on an M4 Pro through MoltenVK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The delta was this frame's position minus the last, and the last started at 0,0, so the
first frame reported the cursor's whole distance from the corner as motion. A cursor-mode
change did the same, switching between a locked cursor's virtual reticle and the real
cursor. Maroon Lake's camera adds mouse_dy to its pitch and came up pointing at the ground.
examples/library/input_mouse_rebase.ludic covers both cases, plus ordinary motion and
re-setting the mode already in force.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The queue was only emptied by the next outline_model call, so the last highlighted tree kept
its rim after the player looked away. r3d_frame now calls outline_frame, which drops a batch
the previous frame closed and nobody reopened.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ov_text, ov_text_w and ov_text_wrap decode UTF-8 instead of drawing bytes 32-126. font.json
may list the atlas's code points in "codes"; an atlas without it reads as before (ASCII
from "first"). A missing code point draws as '?', a cut-off sequence as one '?'.
overlay_font(dir) loads or swaps the atlas at run time, for a language with its own script.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Needle-card crowns are many cut-out quads deep and a discarding shader turns early depth
rejection off, so every card behind the front one ran the full lighting shader. The near
tree LODs now write depth first (depth.frag, the same alpha coverage test), terrain under
them is rejected before shading, and the lit pass draws them with no discard and an equal
depth test (invariant gl_Position). R3D_NOPREPASS=1 restores the old path.
Dense forest on a Windows PC: 61 -> 78 fps at 3840x2160, 116 -> 164 fps at 1920x1080.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
http_link_flags grepped the IR for "@hs_", which every program's header
declares, so `ludic build` linked the HTTP transport and Foundation into
everything - invisible on macOS, a failed link on Linux, where it broke four CI
cases (ludic run, the installed layout, ludic new, the seed build through
`ludic build`). It now looks for a call to hs_send: examples/library/http.ludic
has one, snake.ludic (which still declares ten @hs_ names) has none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The asset-pack boot calls _NSGetExecutablePath, which glibc does not have, so
the Linux link of the compiler seed failed in every CI job - build-and-test,
cfree-fixpoint and every publish run - since packs landed. The last release CI
created was v0.7.0. The Linux shim now defines it over /proc/self/exe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
http_win.ll implements http.ll's hs_* contract over WinHTTP: the request is a
record built on the game thread, the whole exchange runs on a worker thread,
TLS uses the system's certificate checks, and headers are answered from the
kept request handle. ludicc links it with winhttp instead of refusing Http.* on
Windows.
win32.ll decodes the splash and App.set_icon images with WIC (ole32): the
splash is a topmost borderless window at the artwork's size in points times the
display scale, composited over its background colour; the icon becomes an
HICON set on the window now or when win_open makes one.
Verified on the PC: examples/library/http.ludic prints its expected output, a
real GET to https://git.workshopsoft.io/ returns 200 with its body and
Content-Type, and the bundled Maroon Lake shows its splash centred at launch and
its icon in the title bar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The CLI's shell commands go through shell(), which is run() on POSIX and a
scratch script handed to Git for Windows' bash on Windows (exit codes read
directly there). compile_app links through `ludicc -o` on Windows, ludic run
starts the .exe, and ludic-dev build and ensure_ludicc assemble
selfhost/ludicc.win.seed.ll, which ludic-dev reseed now writes beside the
macOS seed. ludic bundle makes build/<name>/ with a GUI-subsystem exe carrying
the .ico beside `app icon` as an llvm-rc resource, game.lpak and packs.index;
ludicc gains --gui and --link, and quotes its whole link line for cmd.exe.
Verified: ludic-dev test 135/135, selfhost-test 32/32; on the PC a checkout
bootstraps from the Windows seed, ludic-dev build makes the toolchain, and
`ludic bundle` makes build/Maroon Lake/, which runs from its own folder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AppKit gives kVK_Delete (51) the character NSDeleteCharacter, 127, which is
what both ev_keyval (the held-key set) and win_poll (the per-frame key)
received, so Key.Backspace, which folds to 8, never matched. Both now map key
code 51 to 8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
compile_app linked gl.ll when the IR named @lgl_* but never http.ll and
Foundation for @hs_*, so examples/library/http.ludic failed to link under
`ludic build` on every hs_* symbol while `ludicc -o` built it. http_link_flags
greps for @hs_ and links them in both modes; ludic-dev test now builds the
example through `ludic build` as well.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The arrow Key constants folded to the codes of w/s/a/d, which is what the
per-frame key reports for an arrow, but cocoa.ll has always stored an arrow in
the held-key set under 128-131. So Input.key_down(Key.Up) read the W bit and
Input.move_i's arrow half never moved anything. Input.key keeps its WASD alias,
so games comparing it with 'w' (snake, chronorift) still take the arrows.
New example input_arrows.ludic, checked by ludic-dev test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A game asking for 960 x 540 got 960 x 540 pixels - a quarter of a 4K panel.
w_fit_scale turns the display's DPI into a whole-number scale (96 -> 1, 168 and
192 -> 2), brought down until the window fits; win_open and win_gl_resize size
the client area with it, win_gl_scale reports it the way cocoa.ll reports the
backing scale, and the mouse still arrives in the game's own units.
Verified on a 3840x2160 panel at 175%: windowed gl_triangle's 640x360 window
draws a 1280x720 frame.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
audio_win.ll implements audio.ll's snd_* contract over XAudio2 from IR: a
source voice per clip, restart on play, LoopCount for loops, SetVolume,
SetFrequencyRatio and a balance pan through SetOutputMatrix, with the COM
slots taken from the SDK's xaudio2.h. Clips are RIFF WAVE read through
lp_pak_open (weakly referenced), so a packed sound loads directly. ludicc links
it with xaudio2 and ole32, and silences xinput.lib's importeddllmain warning.
Verified: ludic-dev test 135/135, selfhost-test 32/32; on the PC a harness
loads, plays, pans, loops and stops clips, and Maroon Lake windowed reports
"sound: 31 of 31 clips loaded".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
win32.ll implements cocoa.ll's contracts over user32 and xinput: the message
pump, the held-key set and frame key in Ludic codes, the mouse with raw input
for cursor mode 2, clip-based cursor modes released on focus loss, XInput pads,
and the software framebuffer present. win32_gl.ll puts the WGL 4.1 core context
on that window (vsync, borderless full screen); gl_win.ll's context code is now
lgl_wgl_core, shared with the headless hidden window, whose win_gl_* stubs move
to gl_win_nowin.ll. audio_win.ll links Audio.* silently for now.
Verified: macOS fixpoint, ludic-dev test 135/135, selfhost-test 32/32; on the
PC gl_triangle renders identically headless and in a real window, and Maroon
Lake builds windowed and runs a playtest to frame 240 in the desktop session.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The UCRT's remove() deletes files only, so a tree removed bottom-up left every
directory behind. emit_win defines remove over DeleteFileA, falling back to
RemoveDirectoryA. Found by Maroon Lake's selftest26 ("1 left behind"), which now
passes on Windows with the rest of that game's self-tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gl_win.ll creates a hidden-window WGL 4.1 core context and carries gl.ll's
float/memory helpers, with ldexp and QueryPerformanceCounter in place of the
libSystem calls. glgen now also writes gl_thunks_win.ll: the same 478 thunks,
calling through pointers that @lgl_win_load fills from wglGetProcAddress (and
opengl32.dll for GL 1.1). ludicc links the pair against opengl32/gdi32/user32
on a Windows target.
Verified: gl_api.ludic and gl_thunks.ll regenerate byte-identically, ludic-dev
test 135/135, selfhost-test 32/32, and headless gl_triangle on an RTX 3070 Ti
matches the macOS frame to within one level per channel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
--target <triple> (default: the host, from OS=Windows_NT) selects the Windows
runtime. emit_win.ludic defines the POSIX names the backend already calls over
the UCRT and Win32 in IR, so rename replaces an existing file, ftell is 64-bit,
and fopen is binary. Known folders follow %APPDATA% / %LOCALAPPDATA% / %TEMP%,
and the driver speaks cmd.exe, writes .exe outputs and finds LLVM's clang.
Verified: macOS three-stage fixpoint, ludic-dev test 135/135, and on Windows
the Mac-emitted Windows IR and the Windows-built compiler's IR are identical
through two generations.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>