In-game material stage editor + editor UI overhaul#895
Open
skies912 wants to merge 182 commits into
Open
Conversation
Extend the editor's Material tab to create, remove, and tune material stages in real time, eliminating the quit/edit-.mat/relaunch cycle. Engine: - collision: Cm_FinalizeStage / Cm_AddStage / Cm_RemoveStage factor the parser's stage-finalize logic and support mutating a material's stage list. - renderer: R_UpdateMaterialStages rebuilds the r_stage_t list (and media) after an add/remove; parameter/flag edits remain live with no rebuild. - cgi: AddMaterialStage / RemoveMaterialStage / FinalizeMaterialStage imports (CGAME_API_VERSION bumped); two new stage notifications. UI: - StageView: one StackView per stage; effects added via a dropdown of available STAGE_* effects, each exposing its parameter sliders; per-row remove; click-to-select reveals a stage's Remove button. - MaterialViewController: Material/PBR/Stages group boxes; Add Stage button; rebuilds the list on add/remove. Param/flag edits write straight to cm_stage_t and render next frame; only add/remove rebuilds the render stage list. Save persists via r_save_materials. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entity tab: - classname is now the group-box title (not an editable row); all remaining keys are wrapped inside that box - Team Master box is shown only for `light` entities - Create/Delete Entity moved off the shared panel accessory onto the Entity tab (bottom-left); Save stays in the shared bar - deleting an entity now selects worldspawn instead of clearing the selection - fix boxes overlapping on Create by re-laying-out the container on selection change (Team Master toggling visibility otherwise left a stale layout) Editor panel: - the tab-page height is computed from the viewport (window_bounds.h - strip - chrome) so the panel fills the window and adapts to resolution, with the action bar pinned at the bottom Global: - group-box title font 12 -> 17 (one point over the 16pt body) so titles read as headers Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e-editor # Conflicts: # src/cgame/default/ui/editor/EntityViewController.c
EditorViewController.c includes ControlsViewController.h / SettingsViewController.h / DialogViewController.h by bare name (matching MainViewController.c). The Windows .vcxproj resolves these via its global UI include path, but the editor's Makefile.am only added -I.../ui, so the Linux build failed with "ControlsViewController.h: No such file or directory". Add -I for ui/common, ui/controls and ui/settings, mirroring main/Makefile.am. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
|
Oh man. The editor files in src/client/ui are all stale! I should have deleted them months ago but I guess I forgot. I'm sure those caused some confusion. You can nuke them in this PR, that would be helpful. I'd do it but I don't want to create conflicts for your branch. |
- New files are located in `quetoo\src\cgame\default\ui\editor`
Collaborator
Author
Done. Yeah that did confuse me. I figured one was newer n you just kept the old for ref/backup or something. Forgot to ask about it. |
Add PKG_CHECK_MODULES([OBJECTIVELYGPU], [ObjectivelyGPU >= 1.0.0]) and bump the ObjectivelyMVC requirement to >= 2.0.0 (the GPU-ported release that renders through ObjectivelyGPU). Thread @OBJECTIVELYGPU_CFLAGS@/@OBJECTIVELYGPU_LIBS@ into the renderer and ui Makefile.am. configure passes all three checks: Objectively 2.0.9, ObjectivelyGPU 1.0.0, ObjectivelyMVC 2.0.0. First step of the OpenGL -> ObjectivelyGPU renderer port. Refs #864. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vet passed: Quetoo's GLSL cross-compiles GLSL -> SPIR-V (glslc) -> MSL (shadercross), including the exotic sampler types, so we keep GLSL and rule out an HLSL rewrite. This establishes the offline transpile pipeline and the SDL_gpu descriptor-set slot map, and converts the shaders that need no material/lighting. uniforms.glsl: restructured as the shared foundation. Per-stage set macros (VERTEX_SHADER/FRAGMENT_SHADER select sampler sets 0/2 and uniform sets 1/3), the globals uniforms_block at binding 0, and a fixed, named fragment sampler map (shadercross preserves the binding indices through to MSL [[texture(n)]], even when sparse). Light machinery (light_t, lights[], active_lights) relocated out to light.glsl in Phase 5, since lights[] (~27 KB) exceeds the 16 KB uniform buffer floor and must become a storage buffer. common.glsl: add an #include guard. Converted depth_pass, draw_2d, draw_3d and post to #version 450, #include, explicit layout(location) on all stage in/out, and per-draw locals UBOs (model, projection2D, post params) at binding 1. Versioned .spv + .metal blobs committed. Build: 'make shaders' in shaders/Makefile.am transpiles each entry point (glslc -D<stage> -> shadercross --msl-version 2.1.0); configure.ac gains AC_CHECK_PROG for glslc and shadercross. Compiled blobs are versioned, so normal builds never invoke the tools. Deferred to Phase 4/5 (C-coupled): depth_resolve (MSAA sampler2DMS resolve), shadow (needs the lights storage buffer), and the material/lighting shaders (bsp, mesh, sky, sprite, decal). Refs #864. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mechanical, behavior-preserving rename ahead of the RenderDevice bring-up: r_context.c/.h -> r_device.c/.h, R_InitContext/R_UpdateContext/R_ShutdownContext -> R_InitDevice/R_UpdateDevice/R_ShutdownDevice, and the r_context_t type and global -> r_device_t / r_device, swept consistently across the renderer, client and ui (and the cgame ABI struct field type). Still OpenGL underneath; the device object itself lands next. Verified: the renderer compiles cleanly (r_device.lo and all renderer units build). The only build failure is ui/QuetooRenderer.c, which is expected and unrelated -- Phase 0 bumped ObjectivelyMVC to 2.0.0 whose concrete Renderer no longer matches the old GL bridge; QuetooRenderer is deleted in Phase 3. Refs #864. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t light)
The atomic OpenGL->ObjectivelyGPU landing: the tree builds and runs on SDL_gpu
(Metal/Vulkan/D3D12) with the UI rendering through ObjectivelyGPU. 3D and the 2D
console are stubbed pending Phase 4/5.
r_device.c: R_InitDevice creates the RenderDevice ($(alloc(RenderDevice),
initWithWindow, window)) with a plain (non-SDL_WINDOW_OPENGL) window -- no GL
attributes, context, GLAD or swap interval -- and a present-target Framebuffer
(swapchain format, color only) set via setFramebuffer. R_ShutdownDevice releases
it. r_types.h: r_device_t.context (SDL_GLContext) -> device (RenderDevice *),
and #include <ObjectivelyGPU.h>.
r_main.c: R_BeginFrame acquires the frame via RenderDevice::beginFrame and clears
the framebuffer with a LOADOP_CLEAR render pass; R_EndFrame presents via endFrame.
R_Init/R_Shutdown bypass every GL subsystem (uniforms, images, models, draw2d/3d,
shadows, sky, post, ...) -- ported back in Phase 4/5. R_DrawViewDepth/MainView/
PlayerModelView are stubbed (early return).
ui: Ui_Init uses WindowController::initWithDevice(r_device.device); Ui_Draw calls
render(commands, framebuffer) with the frame's command buffer and framebuffer, so
the menu composites over the cleared frame (LOADOP_LOAD). QuetooRenderer.{c,h}
deleted -- the old GL Renderer bridge is gone.
Builds clean (quetoo, quetoo-dedicated, quetoo-master). This is the point of no
return: OpenGL is no longer initialized. Refs #864.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cgame creates a scene framebuffer via R_CreateFramebuffer on window/pixel-size events; with GLAD gone that null-derefs glGenFramebuffers. Early-return all of r_framebuffer.c's entry points (GL bodies preserved for the Phase 5 port to the ObjectivelyGPU Framebuffer). The 3D scene framebuffer is unused while 3D is stubbed, so returning a dimensioned, GL-less framebuffer is sufficient. With this the main menu renders through ObjectivelyMVC on Metal. Refs #864. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
r_image_t gains a `Texture *texture` (additive; GL fields kept vestigial so the still-stubbed draw paths compile). R_LoadImage's 2D path creates the texture via $(r_device.device, createTextureFromSurface, surface, SAMPLER) — RGBA32 convert + upload + row-pitch handled by ObjectivelyGPU — and registers the media itself. R_SetupImage / R_UploadImageTarget are retired (early return; GL bodies preserved for reference); R_FreeImage releases the Texture. Cubemap/3D/buffer image types are deferred (loaded with maps → Phase 5). Re-enable R_InitMedia / R_ShutdownMedia: the media cache is a pure Objectively HashTable (no GL) and was wrongly bypassed — that was the R_FindMedia NULL crash. Stub R_GetError_ (glGetError is meaningless without a GL context), no-oping every R_GetError call site. Builds clean. Refs #864. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
R_LoadModel loads MD3/OBJ vertex data into RAM but R_LoadMeshVertexArray then uploads it to the shared GL mesh VAO/VBO (glGenBuffers/glBufferData/…), which null-derefs without GLAD. Early-return it (GL body preserved); the mesh draw is stubbed, so models load without rendering. Ported to a Buffer in Phase 5. Refs #864. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 2D draw subsystem (console, HUD, and the PlayerModelView framebuffer blit via R_Draw2DFramebuffer) is GL and init-bypassed, so any call null-derefs. Early-return all 13 public entry points (R_Draw2D*, R_BindFont, R_SetClippingFrame, R_SetDraw2DProjection, R_StringWidth) — GL bodies preserved for the Phase 5 port to an ObjectivelyGPU pipeline + dynamic Buffer + font Textures. Unblocks the Player Setup tab (PlayerModelView). Refs #864. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vertical slice proving geometry upload + uniforms + pipeline end-to-end, per issue #864. Renders all world BSP elements with the already-converted depth_pass shaders (position + MVP -> flat color) into the present framebuffer, before the full material/lighting BSP program is ported. - r_device.c: register an Objectively ResourceProvider bridging the GPU library's Resource lookups (loadShader) to Quetoo's Fs_Load, so shader blobs resolve via the game's search paths. Give the present framebuffer a D32_FLOAT depth attachment for 3D depth testing. - r_bsp_model.c / r_types.h: port world vertex/index buffers to ObjectivelyGPU Buffer (createBufferWithConstMem). Neutralize the remaining GL in the BSP load path (decal VAOs, voxel light-indices buffer, depth-pass VAO) so a map loads without a GL context. - r_occlude.c: retire glGenQueries during bring-up (Phase 7 Hi-Z). - r_bsp_draw.c: R_InitWorldPipeline/R_DrawWorld/R_ShutdownWorldPipeline — build a GraphicsPipeline from the depth_pass shaders (position-only vertex input, 3D preset, cull disabled for bring-up) and draw the world indexed, pushing globals + identity model as vertex uniforms. - r_main.c: un-stub R_DrawMainView (frustum + uniforms + R_DrawWorld); drop the dead GL uniform-buffer tail from R_UpdateUniforms. - shaders/Makefile.am: install the compiled .spv/.metal blobs alongside the GLSL so the ResourceProvider can find them. Not yet runtime-tested (needs `sudo make install` to deploy the shader blobs). Entities, sprites, shadows, materials/lighting, the dedicated scene framebuffer, and post are later Phase 5 steps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The in-game console draws for a few frames while cls.state == CONNECTING (key_dest == KEY_CONSOLE), before map load. Cl_DrawConsole derives its layout from R_BindFont's char width/height — cl_console.height = (height / ch) - 1 — but the Phase 4 r_draw_2d stub returned ch = 0, so height became -1 and Cl_DrawConsole_Buffer's VLA `char *lines[height]` blew the stack (segfault). This path was never hit at the menu; loading a map is the first time it runs since the 2D stubbing. R_BindFont now reports non-zero placeholder metrics (8x16) while stubbed, keeping console/HUD layout math sane. Nothing is drawn until the 2D path is ported (R_Draw2DString remains stubbed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Loading a map ran R_LoadBspOcclusionQueries -> R_AllocOcclusionQuery, which dereferences r_occlusion_queries.free/.allocated — Lists that are never created because R_InitOcclusionQueries is bypassed during the SDL_gpu port. Segfault at map load. Return NULL for now (the whole occlusion-query subsystem is ported in Phase 7, compute Hi-Z); callers store it as block/light->query and R_FreeOcclusionQuery null-checks. The world draw path bypasses the block/query culling entirely. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two more dormant GL sites first exercised by loading a map with no GL context: - R_LoadSky: dropped the glActiveTexture calls (crash at map load). The cubemap image still loads its SDL surfaces; the GPU texture upload is a no-op until the cubemap path is ported. Sky isn't drawn yet. - R_DrawPost: early-return. It runs right after R_DrawMainView on the first active frame and is entirely GL (bloom/tonemap FBOs). Ported with the scene framebuffer + post pipeline later in Phase 5. Verified the model/sprite load path is already safe (R_LoadMeshVertexArray was stubbed in Phase 4; 2D image uploads go through ObjectivelyGPU). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
R_CompileAtlas errored "Atlas exceeds GL_MAX_TEXTURE_SIZE" because r_config.max_texture_size was 0 — R_InitConfig was bypassed during the port (it queried GL). Reinstated a minimal, GL-free R_InitConfig that reports the SDL_gpu driver and sets conservative device limits (max_texture_size 16384, etc.) that hold across Metal/Vulkan/D3D12. Re-enabled the R_InitConfig() call in R_Init. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
R_CompileAtlas composited the atlas into an SDL_Surface then uploaded it with GL (glTexParameteri/glTexSubImage2D/glGenerateMipmap) — NULL function-pointer crash with no GL context, hit while loading a map's material/sprite atlases (Cl_LoadImages). Create the atlas texture from the composited surface via createTextureFromSurface, like R_LoadImage's 2D path. Uploads level 0 only; atlas mip generation is deferred until the material path samples it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cgame allocates its own scene framebuffer (cg_framebuffer) via cgi.CreateFramebuffer and asserts cg_framebuffer.name in Cg_PrepareView, crashing on the first frame — the bring-up stub left name = 0. Return name = 1 (dimensions were already set) so the assert and the if (name) destroy-guard work. The GL FBO name is unused: the 3D scene renders directly into the device present framebuffer during bring-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
R_OccludeBox dereferenced block->query->result, but R_AllocOcclusionQuery now returns NULL (OQ subsystem retired until Phase 7) and r_occlude defaults to 1, so cgame entity Think handlers calling R_CulludeBox segfaulted during Cg_PopulateScene. Early-return false (nothing occluded) until the query subsystem is ported; also covers R_OccludeSphere, which delegates here. Other query derefs are either null-guarded (r_decal, r_light) or on the bypassed BSP block-draw path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Material textures pack diffuse/normal/specular/tint into a 4-layer 2D array (assembled into a contiguous, layer-major buffer) that was uploaded via the now-stubbed R_UploadImage, leaving material->texture->texture NULL. Create it as a Texture (2D_ARRAY, 4 layers) — and the non-material default case as a plain 2D texture — via createTexture, which uploads all layers in one copy pass. Level 0 only; material mip generation deferred. Groundwork for sampling materials in the BSP program (no render change yet; the bring-up world pipeline still uses depth_pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lights_cached stat was evaluated once after the loop, reading one past the last view light; count it per light instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
r_light_t::source carries the cgame's opaque entity token (matching r_entity_t::id), not a renderer entity pointer -- R_AddEntity copies entities into the view, so comparing against the copy's address never matched, and e.g. the quad damage light was shadowed by its own carrier. This is how main's R_IsLightSource compared, too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bsp_vs runs the shared vertex_lighting() again (clustered voxel + dynamic diffuse, unshadowed): the voxel light data now being a storage buffer, the vertex stage can bind the same lighting buffers the fragment stage does, which the isampler3D scheme precluded. bsp_fragment_lighting blends full per-fragment lighting out to the vertex lighting over [lighting_distance, lighting_distance + LIGHTING_LOD_BLEND_DIST] instead of popping at a hard cut. light.glsl's fragment-only sections (shadow atlas, PCF, fragment_*) are stage-guarded so vertex shaders can include it without inheriting shadow bindings, and voxel.glsl's clustered buffers are opted into via their binding defines. The BSP vertex locals carry the active_lights bitmask alongside the model matrix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Buffer::upload acquires and submits its own command buffer -- an extra queue flush every frame for a 100KB-class copy. A CopyPass on the frame's command buffer sequences the copy with the frame instead, and streams through the device's shared scratch transfer buffer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Consecutive draw elements within (and across) blocks frequently share a material and surface flags; re-binding the pipeline and diffusemap sampler and re-pushing the material uniforms for every draw reached the SDL driver each time, since SDL_gpu performs no deduplication. Track the current material per pass, invalidating on material stage draws. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This reverts commit 95ad541.
Queries are only ever allocated in bulk during a load and invalidated in bulk by the next one, so reset the pool in R_BeginLoading and build the box vertex buffer once in R_EndLoading (R_LoadOcclusionQueries), rather than checking a dirty flag every frame. This also stops the old BSP's free -- which happens in R_EndLoading's stale-media sweep, after the new BSP has allocated its queries -- from resetting the pool out from under the new map. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…loading Collapse the entity/material/mesh selection traces into one shared candidate list (cg_editor.c/.h) so the mouse wheel cycles through overlapping hits on every tab -- fixes decorative surfaces (e.g. a common/dust brush) blocking selection of what's behind them. The cursor now advances once in Cg_HandleEvent and tabs just refresh off it, instead of each tab owning its own trace/candidate state. Rebuild the Mesh tab on KeyValueTableView (matching Entity/Material) and keep it permanently mounted but disabled until a model-bearing entity is selected, replacing the previous add/remove-tab approach that made the tab bar's fill-equally layout ratchet the panel wider with each tab. Add progressive/chunked StageView construction (one per rendered frame via a private pump view) so switching to a heavily-staged material no longer stalls for seconds; Add/Remove Stage now patch the list in place instead of tearing down and rebuilding it.
…and blocks per frame. BSP format change.
…u-branch-dry-run # Conflicts: # src/client/renderer/r_sky.c # src/collision/cm_material.c
Scrollbar, ScrollThumb, KeyValueView, and KeyValueTableView moved upstream into ObjectivelyMVC as generic, reusable classes. Delete the project-local copies and point every consumer at the library headers. MaterialViewController's stage list now binds Scrollbar directly to its StackView's own opt-in scrolling instead of wrapping it in a ScrollView. Also updates the Blend effect's factor dropdown for the jdolan-sdl-gpu-renderer merge: cm_blend_t is renderer-agnostic and needs no GL header, so the dropdown references the real enum instead of duplicating GL values.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Work in Progress
Generated with Claude Code
Summary
Extends the in-game editor with live material stage editing, entity/mesh editing, unified ray selection, and a UI overhaul. Currently merged onto
jdolan-sdl-gpu-rendererto track the Vulkan renderer rewrite, so the diff againstmainincludes that branch's history pending its own merge.Material stage editor
Add/remove/tune material stages (scroll, pulse, envmap, warp, terrain, animation, blend, …) live in-game, then Save to the
.mat. Chunked/progressive stage-view construction avoids the multi-second freeze when switching to a heavily-staged material; Add/Remove Stage patch the list in place instead of a full rebuild. Per-effect editor rows, editable/validated texture paths, live-editable Blend src/dest (updated for the renderer's newcm_blend_tenum).Entity tab
classname is the group-box title, remaining keys wrapped inside it; Team Master box shown only for
lightentities; Create/Delete moved onto the Entity tab; worldspawnsky/messagelive-editable with path validation.Mesh tab
Enabled/disabled based on the current ray-selection (kept always-present but disabled, rather than added/removed, to avoid a tab-bar width ratchet).
Unified ray selection (#840)
One shared, nearest-first candidate list feeds all three tabs (entity def / surface material / mesh model), so the mouse wheel cycles through overlapping hits regardless of the active tab -- fixes decorative brushes (e.g.
common/dust) blocking selection of what's behind them.Editor chrome
Top menu strip, docked panel, viewport-filling panel height derived from
window_boundsso it adapts to resolution.Reusable widgets promoted upstream
Scrollbar/ScrollThumb(bound directly to a StackView's own opt-inscroll) andKeyValueView/KeyValueTableView(two-column key/value rows) were generalized and moved into ObjectivelyMVC itself -- see jdolan/ObjectivelyMVC#43, which this branch depends on.Notes for review
jdolan-sdl-gpu-rendererto track the Vulkan/SDL_gpu rewrite; adjusted the Blend dropdown +Cm_FinalizeStagefor the newcm_blend_tenum, and dropped an obsolete OpenGL texture-bind workaround inr_sky.c(structurally impossible under the new texture model).Testing
Builds clean (Debug, full solution incl.
quetoo.exe/libui) against ObjectivelyMVC (rebased onto its current main) andjdolan-sdl-gpu-renderer. Exercised in-game: stage add/remove/tune/scroll, entity create/delete/edit, mesh tab enable/disable, ray-selection cycling across all three tabs, panel layout at multiple resolutions.