Repository navigation
Render360/iphone baseline lowmem - #5
Open
matthewcodergamer wants to merge 214 commits into
Open
matthewcodergamer wants to merge 214 commits into
matthewcodergamer wants to merge 214 commits into
Conversation
…ne-lowmem Web: Portal-themed start page; glassier, brighter controller
Every control's position and size is measured from the reference layout (iPhone 11 landscape, 896x350 CSS px below Safari's bar) and placed by its centre as a fraction of the page, scaling with the screen. Same shapes: pill shoulders, triggers and Pause/Back/Start, oval face buttons, a rounded rectangular Move pad with chevrons; same dark tinted fills (amber, gray, navy, green, red) with hairline borders and 9px labels. The fills are translucent so the game shows through. The Move pad maps direction against its own width and height since it is not round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…ne-lowmem Web: on-screen controller matches the reference pad one-to-one
Audio: SDL's Emscripten driver runs the audio callback on the browser's
main thread, which never instantiates the engine side module, so Portal
was silent. On the web build snd_dev_sdl.cpp now skips SDL audio, keeps
mixing into its usual ring buffer, and posts the ring's address, size,
m_readPos and m_pauseCount to the page ('callHandler', since main-thread
EM_ASM cannot run side-module JS). An AudioWorklet reads the ring straight
from shared Wasm memory, resamples if the output rate differs, and
advances m_readPos as SDL's callback did. iOS: the first tap resumes the
AudioContext, navigator.audioSession is 'playback' so the ringer switch
does not mute it, and it suspends while the tab is hidden.
+snd_mute_losefocus 0, since the canvas rarely holds DOM focus.
Controller: positions are anchored to thumb zones and the safe area
(measured in the parent page, since env() is 0 inside the iframe):
Move pad bottom-left with LT above, Y/X/B/A level with it bottom-right
with RT above, Pause / Back+Start / LB+RB along the top, Back and Start
flanking the ••• pill. Mirrored margins; sizes and shapes unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…ne-lowmem Web: game audio through an AudioWorklet; thumb-zone controller layout
Text: system-font text (menu, console, HUD) still drew as solid boxes on iPhone, and custom fonts such as the Portal title showed a black box behind white letters: the font pages reach the screen without their alpha. linuxfont.cpp filled every glyph pixel with white RGB and put the shape only in alpha, so with alpha lost each glyph became a box. On the web build it now writes coverage into RGB as well, as Win32Font does, so letters stay readable and additive fonts blend correctly. Fallback fonts are also looked up at the absolute /platform/resource/linux_fonts path pre.js writes them to, independent of the mounted search paths. Diagnostics keep a separate list of error/font/audio lines so the cause of a text or sound problem is not pushed out of the 40-line tail. Full screen: while the game runs the staging page can scroll behind the fixed game layer, and every landscape rotation scrolls it so Safari collapses its address bar. Audio: +volume 0.7 by default; full scale is harsh on a phone speaker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…ne-lowmem iPhone: readable vgui text, auto-collapse Safari bars, softer audio
When a GLSL program failed to link, CGLMShaderPair::SetProgramPair called
glGetShaderiv(GL_INFO_LOG_LENGTH) on the *program* id. Emscripten serves
that through getShaderInfoLog(GL.shaders[id]), so WebGL received something
that is not a shader and threw - the iPhone crash "Argument 1 ('shader')
to WebGL2RenderingContext.getShaderInfoLog must be an instance of
WebGLShader". The program info log is now read with glGetProgramInfoLog
only, and both link and compile failures log one bounded line with the
shader names and the driver's message instead of dumping both full shader
sources through Msg.
Programs failing to link on iPhone also fits the menu text drawing as
boxes; the new lines land in the diagnostics' error list so the driver's
reason is visible on the next run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
New Game: the dialog lists chapters with FindFirst("cfg/chapter*.cfg").
Directory listings come from the main thread's shared FS, which only had
gameinfo and VPK placeholders; the user's loose cfg/scripts/resource/maps
files were linked on the Source worker only. The chapter list was empty
and "Start new game" had nothing to start. post.js now adds zero-byte
names for those loose files too; opens and stats are still intercepted by
filesystem_stdio and served from the real File.
Text: the font page reaches the screen with alpha read as opaque, so each
glyph drew on a black box. Glyph coverage is already in RGB on the web
build, so the procedural font materials now blend additively there; the
box disappears whether or not alpha survives. Bitmap fonts keep alpha
blending.
Audio: the AudioWorklet kept reading the ring even when the engine had not
painted new samples, replaying stale contents - the buzz. The engine now
publishes the sample time it has written (m_paintedEnd) and the reader
never plays past it, outputting silence instead. snd_mixahead 0.25 gives
slower phone frames more headroom inside the 0.37 s ring.
Rendering: mat_bumpmap/mat_specular stay on; turning them off made Portal
materials request a $bumpmap texture that was never loaded (red console
errors), and the crash they were meant to avoid was the togles link-log
bug.
Controller: works on the menus. Menu mode (background map, pause menu):
stick sends arrow keys with auto-repeat, A/Start/X Enter, B/Back Escape,
LB/RB left/right. Game mode once a real map loads; Pause toggles between
the two. It stays visible on the menus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…pad fades New Game now reaches testchmb_a_00, but ~75 s into loading the level the engine thread died with an out-of-bounds access inside the main module's low-numbered runtime functions (allocator range) - the signature of heap memory overwritten earlier. Level load is where Source recurses deepest (BSP and physics collision building), and under PROXY_TO_PTHREAD the engine thread gets STACK_SIZE, which was 4 MB against Linux's 8 MB. A silent stack overflow into the heap fits. STACK_SIZE is now 16 MB. To make the next crash, if any, conclusive: modules and the main module are linked with --profiling-funcs so Safari's Wasm stacks carry function names (the CI ban on it is lifted while crashes are traced), and the worker crash stack is kept whole (40 frames, 3000 chars) instead of being cut at 300 characters. Controller: fades to 16% opacity after 4 s without a touch and comes back on any touch; never fades while a control is held. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
With function names in the build, the iPhone crash finally resolved to: CInputSystem::FingerEvent <- TouchSDLWatcher <- SDL_PushEvent <- SDL_SendTouch <- Emscripten_HandleTouch. The earlier "~30 s" out-of-bounds crashes had the same frame indices: they were the player's taps. FingerEvent used SDL's finger id directly as the index into m_touchAccumX/Y and only checked the upper bound. SDL ids are arbitrary 64-bit values; Android gives 0,1,2..., but in a browser they are the DOM Touch.identifier, which on iPhone Safari is large. Truncated to int it is often negative, so every tap wrote outside the arrays. TouchSDLWatcher now assigns each live finger a slot 0..9 on down/motion and frees it on up; FingerEvent and GetTouchAccumulators reject anything outside 0..TOUCH_FINGER_MAX_COUNT-1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…olors) New Game now loads testchmb_a_00 and renders the chamber on iPhone, but props drew with green/blue garbage and the frame crashed in CMeshDX8::SetColorStreamState <- CMeshDX8::Draw <- R_StudioDrawGroupHWSkin <- DrawModelStaticProp <- CStaticPropMgr::DrawStaticProps: the static prop's color mesh pointer was invalid. Static prop vertex lighting (.vhv) is read through the async filesystem into DataCache-managed, pooled color meshes; in the browser build props were drawn with color meshes that were not valid yet or already released. On the web build r_proplightingfromdisk now defaults to 0, so no color meshes are created and props use regular model lighting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
The game rendered the menu and the first chamber at 640x480 (the device log said BackBufWidth 640) and the page stretched that 4:3 picture over the 2.16:1 iPhone screen: zoomed, horizontally squashed 3D and oversized menu text, unlike real Portal's wide view. FindVideoMode only honours a custom size if it equals the size requested when the window was created and the game is windowed; anything else falls back to the display mode list, and for a size not in that list, to mode 0 (640x480). A browser canvas has no mode list, so on the web build it now always returns the requested size. ResetCurrentModeForNewResolution logs the chosen mode ([Render360 video]) into the diagnostics error list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…n web WebGL can't map GPU buffers. Emscripten's emulation hands back fresh, uninitialized malloc memory on every map and uploads all of it on unmap, which broke three paths in the browser: - Lightmap pages are dynamic textures that stream through a mapped pixel buffer. Every re-lock of a page re-uploaded the whole page from garbage, wiping the surfaces written earlier: the world rendered blown-out white with cyan/green noise. On web, dynamic textures now keep a persistent CPU backing store (zero-initialized) and upload only the locked region. - Static vertex/index buffer locks got uninitialized memory, so any bytes the engine didn't rewrite became garbage. On web the locked range is staged in CPU memory holding the buffer's current contents (read back with glGetBufferSubData when needed) and uploaded on unlock. - Static prop color meshes shared one pooled vertex buffer that is locked for N vertices and then written past that range. Pooling is off on web, so every prop gets its own color mesh (green/cyan garbage on props). Also: - DXT decoding for tiny mips (2x2, 1x1) used the real width as the row stride and mistook 2x2 DXT5 for uncompressed data; decode into whole blocks and upload with the padded row length. - The requested -w/-h was clamped to a fixed 640x480 "display mode" in the browser, so the game still ran stretched 4:3. Keep the requested size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…y option After a deploy Safari can load the start page before the updated service worker controls it, so the page has no COOP/COEP headers, no SharedArrayBuffer, and the engine dies with "The object can not be cloned" on a black screen. The start page used to reload only once per tab; it now reloads whenever isolation is missing (at most once every 20 s, so browsers that can't isolate don't loop), and the game page does the same instead of starting without threads. Graphics quality (start page segmented control, and "Graphics" in the in-game ••• menu, applied on next launch): - Standard: 540p, quarter-size textures (previous behaviour, default) - HD: 720p, half-size textures - Max: full screen resolution, full-size textures The chosen quality is included in Copy diagnostics. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
With the engine now asking for the page-sized mode (1168x540 on an iPhone 11), CShaderDeviceMgrDx8::ValidateMode compared it with the GL layer's fixed 640x480 "display mode" and refused it. SetMode failed, CreateGameWindow returned false, ModInit failed and the engine exited right after "[Render360 video] mode 1168x540" with RUN_OK and nothing on screen. A browser canvas can be any size, so skip the windowed size check on web. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…game) WebGL 2 has no glDrawRangeElementsBaseVertex, so the browser build's DrawIndexedPrimitive computed a base-vertex-adjusted attribute setup in FlushVertexBindings, but that setup is only latched (m_drawVertexSetup is unused). FlushDrawStates rebuilt the GL vertex attribs from the bound streams with a base vertex of 0, so every indexed draw with a non-zero BaseVertexIndex read the wrong vertices: triangles stretched across the screen, stair-stepped smears, blown-out white surfaces and saturated blue/green/cyan from mismatched positions, texcoords and colors. On web, pass BaseVertexIndex through to FlushDrawStates and start every vertex attribute that many vertices into its stream; the base vertex is part of the attrib-cache key so it is re-applied whenever it changes. Native builds are unchanged (they pass it to glDrawRangeElementsBaseVertex). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…tics In game, moving the camera leaves stacked, ghosted copies of geometry and colours run to white/blue/cyan, while still frames (menu, pause) look right: something from earlier frames survives into later ones. - Run with gl_clear 1, so the client clears the colour buffer at the start of every view and nothing from a previous frame can carry over. - Add a GL probe in the render worker: it shadows the state the engine sets through WebGL and, for a few frames (5, 30, 120, 300, 600, then every 900), logs draws, blended draws and their factors, draws without depth test or depth writes, every clear with the scissor/mask state it ran under, framebuffer switches and glGetError, as "[Render360 GL]" lines in Copy diagnostics. It needs no GPU queries per draw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
The probe showed the in-game trails are gone (frames are cleared), but a GL error (0x500, INVALID_ENUM) every frame, lit world surfaces rendering black, and a freeze after the first chamber loads. Extend it so the next diagnostics pin all three down: - Error hunt: for three short windows (frames 6-8, 200-203, 450-453) every WebGL call is followed by glGetError and the rejected calls are logged with their arguments. The wrappers are installed only for the window and removed after, so normal frames pay nothing. - Texture uploads: the first texSubImage2D/texImage2D calls are logged with size, format, UNPACK_ROW_LENGTH and a sample of their bytes (mean, share of non-zero), to see whether lightmap data reaches WebGL. - Heartbeat: an "alive" line with frame count and fps about every 10 s, so a freeze shows as the moment the lines stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…robe The GL probe named the calls WebGL rejects in game: - texImage2D(GL_RGB8 internal format, GL_RGBA data) -> INVALID_OPERATION. D3DFMT_X8R8G8B8 is 4-byte BGRX data kept in a 3-channel GL_RGB8 texture, a pairing GLES 3 / WebGL 2 do not allow, so every such texture (normal maps, the engine's 1x1 white/grey/black fallback textures) had no image and sampled black. On web X8R8G8B8 is reported unsupported, so the material system converts those images to R8G8B8 when it loads them; convert_texture also promotes RGB8 to RGBA8 whenever 4-channel data arrives, so no texture is left without an image. - texParameter(GL_TEXTURE_BORDER_COLOR), the GL_CLAMP_TO_BORDER wrap mode and enable/disable(GL_FRAMEBUFFER_SRGB) -> INVALID_ENUM every frame. WebGL has none of them: skip the border colour, clamp to edge instead, and leave the sRGB write to the shader (as the web build already does). Freeze: frames stopped about 12 s into the first chamber. The probe now logs frames slower than 1.5 s with their draws, uploads and GPU buffer read-backs, counts read-backs in the heartbeat, adds the framebuffer status to INVALID_FRAMEBUFFER_OPERATION errors, and the page logs when the render thread stops presenting frames and when it resumes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
…ndows The probe showed clears and draws failing with framebuffer status 0x8CD9 (FRAMEBUFFER_INCOMPLETE_DIMENSIONS): WebGL 2 still requires every attachment of a framebuffer to have the same size, while Source renders into smaller targets (1024x512, 292x135, ...) with the full-screen 1168x540 depth buffer bound, as D3D9 allows. Everything drawn into those framebuffers was lost. On web such a framebuffer now gets a private depth buffer of the colour target's size (created once per size); those passes only touch the top-left of the big depth buffer and clear depth anyway. The menu also ran at 6-12 s per frame in the last run. Add profile windows (frames 11-13, 60-63, 240-243, or up to a slow frame) that count WebGL calls by name and time spent inside them versus the whole frame, so the next diagnostics show whether the time goes to WebGL or to the engine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy5DKdwTqGQqScWvJyNcft
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.
No description provided.