2 Commits

Author SHA1 Message Date
ml e0a2e1f4bf Fix persistent GL_INVALID_OPERATION spam and resolution-change bugs on Android
build / build (push) Successful in 2m44s
- opengl_end_frame() bound framebuffer 0 on Android, which has no valid
  target there (real presentation goes through the OpenXR swapchain's own
  FBOs) - gl4es reported this every frame. Skip it there.
- render_hack_cameras() re-entered opengl_start_frame()/opengl_end_frame()
  for camera-monitor renders that never touch the GL screen; excluded them
  to match use_opengl()'s existing behavior. Also drains a stray error left
  by xrPollEvent().
- ChangeScreenSize() never called opengl_resize() on Android, freezing the
  3D render target at launch size after an in-game resolution change; and
  xr_get_game_resolution() used the swapchain's fixed size instead of the
  current screen resolution, misplacing the laser-pointer cursor after such
  a change.
- Removes all temporary bisection diagnostics added while tracking down the
  GL_INVALID_OPERATION source.
2026-09-04 16:34:52 +02:00
ml 504ed3e1cb Fix SFX and MIDI music not playing on Android
snd_start_digital() opened two separate SDL audio devices - a raw one
for cutscene/audiolog audio and a second via Mix_OpenAudio() for
SFX/MIDI - but Android's audio backends only ever allow one open
non-capture device, so the second open always failed silently.

- Consolidate onto a single Mix_OpenAudio() device; cutscene/audiolog
  PCM is now mixed into its output via Mix_SetPostMix() instead of a
  dedicated device (16-android-audio-single-device.patch)
- Add SDL_GetError() to the failure log so a future open failure is
  actually diagnosable (15-android-audio-error-detail.patch)
- Make NativeMidiInit() report failure on platforms with no real
  implementation (Android included), instead of silently claiming
  success without opening anything (17-android-native-midi-unsupported.patch)
- Strengthen the OpenSL ES driver hint to SDL_HINT_OVERRIDE and log the
  active audio driver as a standing diagnostic (05-android-audio-driver.patch)
2026-08-30 07:09:16 +02:00
15 changed files with 1142 additions and 21 deletions
+313 -8
View File
@@ -383,10 +383,14 @@ each one does:
emulation) for real GLES-native equivalents: a custom `pointSize` emulation) for real GLES-native equivalents: a custom `pointSize`
uniform, a shader-side alpha discard, and native point-sprite uniform, a shader-side alpha discard, and native point-sprite
rasterization. rasterization.
- **`05-android-audio-driver.patch`** - forces SDL2's older OpenSL ES - **`05-android-audio-driver.patch`** - forces SDL2's OpenSL ES audio
audio backend instead of AAudio, because AAudio only allows one open backend instead of AAudio via
playback device and the engine opens two (cutscene audio plus `SDL_SetHintWithPriority(..., SDL_HINT_OVERRIDE)` (so nothing else can
`Mix_OpenAudio` for SFX/MIDI). take precedence and silently leave AAudio selected), and logs the
actual `SDL_GetCurrentAudioDriver()` result right after `SDL_Init()`
as a standing on-device diagnostic. Does not by itself fix SFX/MIDI
playback - see `16-android-audio-single-device.patch` and README 5.5.3
for the actual cause and fix.
- **`06-android-resize-event.patch`** - makes the engine react to - **`06-android-resize-event.patch`** - makes the engine react to
`SDL_WINDOWEVENT_RESIZED`, not just `SIZE_CHANGED`, since Android's SDL `SDL_WINDOWEVENT_RESIZED`, not just `SIZE_CHANGED`, since Android's SDL
backend only ever sends the former for surface-driven resizes. backend only ever sends the former for surface-driven resizes.
@@ -398,6 +402,10 @@ each one does:
`SDL_SetWindowFullscreen`/`SetWindowSize`/`SetWindowPosition` calls on `SDL_SetWindowFullscreen`/`SetWindowSize`/`SetWindowPosition` calls on
Android, since there's no real desktop-style window to resize and Android, since there's no real desktop-style window to resize and
calling them desyncs SDL's cached window size from the real surface. calling them desyncs SDL's cached window size from the real surface.
Also calls `opengl_resize()` directly in `ChangeScreenSize()`
(see README 5.5.6) - skipping `SDL_SetWindowSize()` means no native
resize event ever fires on Android to trigger it when the in-game
resolution changes, so it would otherwise never re-run there.
- **`10-android-openxr-cmake.patch`** - wires up the OpenXR loader plus - **`10-android-openxr-cmake.patch`** - wires up the OpenXR loader plus
EGL as link/include dependencies for the CMake build. EGL as link/include dependencies for the CMake build.
- **`11-android-openxr-present.patch`** - the core OpenXR present-path - **`11-android-openxr-present.patch`** - the core OpenXR present-path
@@ -434,6 +442,72 @@ each one does:
inherited non-default unpack state left behind elsewhere in the frame inherited non-default unpack state left behind elsewhere in the frame
and misread every row. Fixed by resetting both to the packed defaults and misread every row. Fixed by resetting both to the packed defaults
immediately before this upload. immediately before this upload.
- **`15-android-audio-error-detail.patch`** - `snd_start_digital()`'s
`Mix_OpenAudio()` failure only logged a fixed string, not the actual
reason. Adds `SDL_GetError()` to the log line so the next on-device
failure reports the real underlying error.
- **`16-android-audio-single-device.patch`** - fixes SFX/MIDI music not
playing (see README 5.5.3). `snd_start_digital()` opened two SDL audio
devices: a raw `SDL_OpenAudioDevice()` for cutscene/audiolog audio and
a second one via `Mix_OpenAudio()` for SFX/MIDI - SDL's Android audio
backends only ever allow one open non-capture device, so the second
open always failed. Removes the raw device entirely and feeds cutscene/
audiolog PCM into the `Mix_OpenAudio()` device's output instead, via
`Mix_SetPostMix()` (mixing in rather than overwriting, so it doesn't
silence any SFX/MIDI music playing at the same time). `cutsloop.c`'s
and `audiolog.c`'s `SDL_PauseAudioDevice()` calls (no longer meaningful
without a dedicated device) are removed; the cutscene-audio-stream
pointer swaps they guarded are now protected by `SDL_LockAudio()`/
`SDL_UnlockAudio()` instead, since SDL_mixer's audio thread now reads
that pointer continuously via the post-mix hook.
- **`17-android-native-midi-unsupported.patch`** - `NativeMidiInit()` in
`MusicDevice.c` has real implementations only under `#ifdef WIN32` and
`#elif defined(USE_ALSA)`; on every other platform (Android included)
it fell through to `return 0` (success) without ever opening a device,
so a `midi-backend` preference of "Native" (see README 5.5.4) played
silently forever instead of falling back. Adds an `#else` branch that
returns failure and logs a `WARN`, so `InitDecXMI()`'s existing
fallback-to-dummy-driver logic actually triggers.
- **`18-android-opengl-immediate-mode-attribs.patch`** - fixes a black (and,
after this patch's first revision, flat-colored/untextured) screen when
switching to the OpenGL/3D render mode in-game (see README 5.5.5). Every
`glBegin(GL_TRIANGLE_STRIP)`-based draw in `OpenGL.cc` -
`opengl_swap_and_restore()`'s screen-composite quad,
`opengl_light_tmap()` (every textured wall/floor polygon, via
`draw_vertex()`), `opengl_bitmap()` (sprites), and `opengl_draw_poly()`
(flat/gouraud polygons, also via `draw_vertex()`) - used
`glVertexAttrib2f`/`glVertexAttrib1f`/`glVertex3f` immediate mode to set
per-vertex texcoords/light, which gl4es only captures per-vertex for
attribute 0 (position); custom attributes end up constant for the whole
draw call. Adds `android_imm_begin()`/`android_imm_vertex()`/
`android_imm_end()`, a small shared immediate-mode-emulation shim that
buffers vertices and flushes them as one real `glDrawArrays()` call, and
switches every one of those call sites to it, Android-only. The
starfield (`opengl_begin_stars()`/`opengl_draw_star()`/
`opengl_end_stars()`, `GL_POINTS` rather than `GL_TRIANGLE_STRIP`, up to
`NUM_STARS`=1000 per frame) had the same bug for its per-star brightness
and gets its own similarly-shaped `android_stars_begin()`/
`android_stars_vertex()`/`android_stars_end()` buffer. Also skips the
unconditional `SDL_GL_MakeCurrent(window, context)` call every
rendering function in this file had at its top, Android-only - a
worthwhile cleanup on its own merits, but *not* the source of the
persistent per-frame `GL_INVALID_OPERATION` (1282) logcat spam during
3D gameplay that this was first suspected to be (see README 5.5.8 for
that bug's actual root cause and fix, in patch 20 below).
- **`19-android-fix-hackcam-opengl-frame.patch`** - `fr_start_view()`/
`fr_send_view()` (`frsetup.c`) called `opengl_start_frame()`/
`opengl_end_frame()` again for hack-camera security-monitor renders
(which target a private software bitmap, not the GL-backed screen),
since `should_opengl_swap()` doesn't exclude them the way
`use_opengl()` already does. Adds the same exclusion to both call
sites (see README 5.5.8).
- **`20-android-no-default-framebuffer.patch`** - the actual fix for the
persistent per-frame `GL_INVALID_OPERATION` (1282) in README 5.5.8:
`opengl_end_frame()`'s `BindFrameBuffer(nullptr)` bind to framebuffer/
renderbuffer id 0 has no valid target on Android (real presentation
goes through the OpenXR swapchain's own FBOs, set up separately in
`xr_swapchain.c`) and is also functionally dead there regardless -
skipped entirely, Android-only.
`android/gl4es-patches/`: `android/gl4es-patches/`:
@@ -517,10 +591,241 @@ left behind elsewhere in the frame and misreads every row - a diagonal
shear/banding pattern. Menus never trigger it because nothing in the shear/banding pattern. Menus never trigger it because nothing in the
menu-only code paths sets a custom unpack stride first. menu-only code paths sets a custom unpack stride first.
**Fix** (`14-android-quad-unpack-state.patch`): explicitly reset both #### 5.5.3. In-game SFX and MIDI music didn't play
`GL_UNPACK_ROW_LENGTH` and `GL_UNPACK_ALIGNMENT` to their tightly-packed
values immediately before this `glTexImage2D` call, removing the Intro cutscene audio played correctly, but in-game SFX and MIDI/background
dependency on whatever GL state preceded it. music never played, on-device.
**Root cause:** `snd_start_digital()` in `engine/src/MacSrc/SDLSound.c`
opened two separate SDL audio devices - a raw `SDL_OpenAudioDevice()`
call (used for cutscene/movie audio, always succeeded) and a second
device via `Mix_OpenAudio()` (used for SFX and, via `Mix_HookMusic`,
MIDI music - always failed, `SDL_GetError()` reporting "Audio device
already open"). SDL's Android audio backends (both AAudio and OpenSL ES)
only ever allow one open non-capture device - forcing OpenSL ES instead
of AAudio (`05-android-audio-driver.patch`) doesn't change this, since
the restriction isn't backend-specific.
**Fix (`16-android-audio-single-device.patch`):** removed the raw
`SDL_OpenAudioDevice()` call entirely. Cutscene/audiolog PCM (previously
delivered via that device's own callback) is now mixed into the single
`Mix_OpenAudio()` device's output via `Mix_SetPostMix()`, additively
(`SDL_MixAudioFormat`) rather than overwritten, so SFX/MIDI music
already mixed into the buffer isn't silenced whenever a cutscene or
audiolog plays. The stream-pointer swaps in `cutsloop.c`/`audiolog.c`
that used to be guarded by pausing the now-removed device are guarded by
`SDL_LockAudio()`/`SDL_UnlockAudio()` instead, since the post-mix hook
runs continuously on SDL_mixer's own audio thread.
#### 5.5.4. MIDI music silent even after audio otherwise worked
After fixing 5.5.3, SFX and cutscene audio played correctly, but MIDI
background music was still silent, with logcat showing `Creating native
MIDI device` instead of `Creating FluidSynth MIDI device`.
**Root cause:** the engine's `midi-backend` preference (`0` = ADLMIDI,
`1` = Native, `2` = FluidSynth) is persisted in a `prefs.txt` file in the
app's private storage, which Android retains across every APK
reinstall/upgrade (only a full uninstall clears it). A stale value of
`1` (Native) from earlier testing was carried forward through every
rebuild this whole session. `SetDefaultPrefs()` (`Prefs.c`) does default
to `2` (FluidSynth) whenever `USE_FLUIDSYNTH` is defined, which it
always has been for this build - the default was never wrong, only the
persisted override. `NativeMidiInit()` has no real implementation on
Android (see `17-android-native-midi-unsupported.patch`), so it silently
reported success without opening anything.
**Fix:** a full uninstall (not just installing a new build over the old
one) clears the stale preference, after which MIDI plays correctly.
`17-android-native-midi-unsupported.patch` additionally makes
`NativeMidiInit()` report failure on unsupported platforms, so a stale
"Native" preference falls back to `InitDecXMI()`'s existing dummy-driver
path (still silent, but with a clear `WARN` in logcat) instead of
silently reporting success while never opening anything - a much faster
diagnosis if this ever recurs.
#### 5.5.5. Black, then flat-colored/untextured screen after switching to OpenGL rendering in-game
The engine has a `Ctrl+G`-bound in-game toggle (`toggle_opengl()`,
`Prefs.c`) that cycles between the plain software renderer and OpenGL
rendering. On Quest, switching to OpenGL rendering (either mode:
unfiltered or bilinear) produced a black screen for the 3D view, with
`OpenGL error: 1282` (`GL_INVALID_OPERATION`) in logcat right at the switch.
**Root cause:** `opengl_swap_and_restore()` draws the offscreen-rendered
3D view onto a screen-filling quad using `glBegin(GL_TRIANGLE_STRIP)` /
`glVertexAttrib2f()`/`glVertexAttrib1f()` / `glVertex3f()` immediate
mode to supply per-vertex texcoords and light. This is exactly the same
bug already found and fixed for the UI-overlay quad in
`11-android-openxr-present.patch`'s `android_draw_surface_as_quad()`:
under gl4es's GLES2 immediate-mode emulation, `glVertexAttrib*()` sets a
*constant* value for the whole draw call, not a fresh value per vertex
the way desktop GL's immediate mode does - only attribute 0 (position,
driven by `glVertex3f`) gets real per-vertex capture. The 3D-view quad
draw predates that fix and was never updated to match, so it kept using
the broken pattern.
This patch's first revision only fixed that one quad, which did clear the
black screen - but the next on-device test showed a "blocky" world
instead: every wall/floor polygon rendered as a single solid color
instead of its texture. The same broken immediate-mode pattern turned out
to be used by every other textured/lit polygon draw in the file too:
`opengl_light_tmap()` (every textured wall/floor polygon in the 3D view,
via the shared `draw_vertex()` helper), `opengl_bitmap()` (sprites), and
`opengl_draw_poly()` (flat/gouraud-shaded polygons, also via
`draw_vertex()`) - each polygon's texcoord/light attribute ended up
constant across all of its vertices (whatever was last set), instead of
varying per vertex, hence the flat, solid-colored blocks.
**Fix:** `18-android-opengl-immediate-mode-attribs.patch` adds
`android_imm_begin()`/`android_imm_vertex()`/`android_imm_end()`, a small
shared immediate-mode-emulation shim (buffers vertices, then issues one
real `glDrawArrays()` call), and switches every glBegin/glVertexAttrib/
glVertex3f call site above to it, Android-only - the desktop path is
unaffected and keeps the original immediate-mode code, which works
correctly under real desktop OpenGL.
The same patch also fixes the starfield
(`opengl_begin_stars()`/`opengl_draw_star()`/`opengl_end_stars()`), found
while auditing the file for every other instance of this pattern rather
than through an on-device symptom: each star is a separate `GL_POINTS`
vertex with its own brightness set via the same
`glVertexAttrib1f`/`glVertex3f` immediate-mode pair, so every star drawn
between one begin/end call would have rendered at whatever brightness was
set for the last one. Gets its own `android_stars_begin()`/
`android_stars_vertex()`/`android_stars_end()` buffer, shaped for
`GL_POINTS` and up to `NUM_STARS` (`GameSrc/rendtool.c`) vertices rather
than `android_imm_*()`'s small triangle-strip buffer.
#### 5.5.6. Display breaks and stops responding to input after changing resolution in-game
Picking a different resolution from the in-game options menu (e.g.
`1024x768`) produced a single `OpenGL error: 1282` in logcat and left the
display frozen on the last frame - `VrApi`'s FPS counter kept running at
90fps (the OpenXR compositor was still submitting frames), but nothing
in the menu responded to further input afterward.
**Root cause:** `ChangeScreenSize()` (`ShockBitmap.c`) calls
`SDL_RenderSetLogicalSize()` to the new resolution and reallocates
`drawSurface`/`offscreenDrawSurface` accordingly, but on desktop the
actual GL-side work - `opengl_resize()` in `OpenGL.cc`, which
(re)creates `backupBuffer` (the offscreen render target the 3D view
draws into) at the new size - only runs later, from the
`SDL_WINDOWEVENT_RESIZED`/`SIZE_CHANGED` event that
`SDL_SetWindowSize()` generates. `09-android-no-window-resize.patch`
skips that `SDL_SetWindowSize()` call on Android for an unrelated
reason (see its own entry above) - which also means no such event, and
therefore no `opengl_resize()` call, ever happens on Android when the
in-game resolution changes. `backupBuffer` stayed stuck at whatever size
it was created with at launch while the rest of the game moved on to
the new resolution, and that mismatch is what broke rendering and left
the menu unable to correctly hit-test further clicks.
**Fix:** `ChangeScreenSize()` now calls `opengl_resize()` directly on
Android, right after updating `gScreenWide`/`gScreenHigh` (matching the
desktop event's actual timing, since `SDL_PollEvent()` always delivers
it after `ChangeScreenSize()` has already returned) - added to
`09-android-no-window-resize.patch` since this is a direct, foreseeable
consequence of that patch's own change, not a new mechanism.
#### 5.5.7. Laser-pointer cursor disappears after changing resolution in-game
After 5.5.6's fix, changing resolution stopped breaking the display, but
the laser-pointer-driven mouse cursor (see section 5's architecture
overview) would disappear and stop being able to click anything
afterward.
**Root cause:** `xr_get_game_resolution()` (`xr_session.c`), which
`xr_input.c` uses to convert a game-quad ray hit's normalized u,v into
the pixel coordinates it feeds the engine via `xr_mouse_move()`, returned
the game quad's OpenXR swapchain's fixed pixel size
(`g_game_swapchain.width/height`) - set once at launch and never resized.
`android_draw_surface_as_quad()` (`OpenGL.cc`,
`11-android-openxr-present.patch`) always stretch-blits `drawSurface` to
fill that swapchain image via a single full-quad draw, regardless of
`drawSurface`'s own pixel size, so the swapchain's fixed size has no
actual relationship to the coordinate space `MouseX`/`MouseY` live in -
that's `drawSurface`'s own current size (`gScreenWide`/`gScreenHigh`),
which `ChangeScreenSize()` can change at runtime independently of the
swapchain. Using the stale swapchain size there mapped every ray hit
into the wrong coordinate space, moving the mouse position out of the
engine's actual screen bounds.
**Fix:** `xr_get_game_resolution()` now returns `gScreenWide`/
`gScreenHigh` instead. `xr_get_game_quad_extent()` (the quad's shape and
distance in the VR world) is intentionally left as-is, still tied to the
swapchain's fixed size - that's a real, unchanging property of the
swapchain image itself, unrelated to this bug.
#### 5.5.8. Persistent `OpenGL error: 1282` logcat spam during 3D gameplay
Every logcat capture during actual 3D gameplay showed a continuous
stream of `OpenGL error: 1282` (roughly twice per frame) from the one
pre-existing `glGetError()` check in `opengl_swap_and_restore()`, with
no visible effect on rendering (textures, resolution changes, and the
cursor all worked correctly per sections 5.5.5-5.5.7's fixes). 1282 is
`GL_INVALID_OPERATION` (0x0502) - not `GL_INVALID_ENUM` (0x0500/1280),
which earlier debugging had mistakenly assumed given the visual
symptoms resembled the immediate-mode bug in 5.5.5.
Two real, unrelated bugs turned out to be involved, found by adding a
temporary chain of `glGetError()` checkpoints across the whole
per-frame render path (`mainloop()` → `render_run()` → `fr_rend()` →
`fr_prepare_view()`/`fr_start_view()`/`fr_send_view()`) and bisecting
which ones read clean:
**xrPollEvent() leaves a stray error.** `xr_poll_events()`
(`xr_session.c`) left gl4es reporting `GL_INVALID_OPERATION` on every
call while the session is running, regardless of what this app's own
code does. Fixed by draining it (`while (glGetError() != GL_NO_ERROR)`)
right after the polling loop.
**A hack-camera render re-entered the OpenXR frame setup.**
`render_hack_cameras()` (`render.c`) calls `fr_rend()` - and so
`fr_start_view()`/`fr_send_view()` - a second time per real frame for
each active security-camera monitor, rendering into a private software
(`FR_DOUBLEB_MASK`) bitmap rather than the actual GL-backed screen.
`use_opengl()` already excludes hack-camera/pickup-mode views from the
GL texture-mapping functions (`FR_PICKUPM_MASK|FR_HACKCAM_MASK`), but
`should_opengl_swap()` - the separate check gating
`opengl_start_frame()`/`opengl_end_frame()` in those two functions -
didn't, so those ran again mid-frame for a render that was never
GL-backed. `19-android-fix-hackcam-opengl-frame.patch` adds the same
exclusion to both call sites. This is a genuine bug fixed on its own
merits, but bisecting further showed it wasn't actually the source of
the 1282 spam - removing it just relocated the checkpoint that caught
the error, without changing whether it fired.
**Actual root cause: binding "framebuffer 0" on Android has no valid
target.** `opengl_end_frame()`'s `BindFrameBuffer(nullptr)` binds
framebuffer/renderbuffer id 0 to mean "the real window surface" - true
on desktop, where that's SDL's actual GL window. On Android, this app's
real presentation target is never that window's own EGL surface at
all - it's the OpenXR swapchain's per-image FBOs
(`xr_swapchain_create()`/`xr_swapchain_acquire()`, `xr_swapchain.c`).
Whatever framebuffer/renderbuffer 0 actually resolves to for gl4es in
this configuration, binding it reliably set a persistent
`GL_INVALID_OPERATION` - confirmed by bisecting through the *entire*
render path with per-call checkpoints, all clean right up until the one
placed immediately after this exact call. The bind is also functionally
dead regardless of the error: by the time anything next draws, Shock.c's
`SDLDraw()` has already run `xr_frame_begin()` →
`xr_swapchain_acquire()`, which rebinds to that frame's real swapchain
FBO before `opengl_swap_and_restore()` (the only thing that draws before
the next `opengl_start_frame()`'s own `BindFrameBuffer(&backupBuffer)`)
runs - so nothing ever actually renders while framebuffer 0 is bound.
`20-android-no-default-framebuffer.patch` skips the call on Android
entirely. Confirmed via logcat: this fully eliminated the error across
an extended play session with 3D rendering active throughout, with no
change in what actually renders.
An earlier theory (that the OpenXR runtime's own GL work inside
`xrAcquireSwapchainImage`/`xrWaitSwapchainImage`/`xrEndFrame` was
leaving stray driver-level errors on the shared EGL context) prompted a
dlsym'd, gl4es-bypassing `glGetError()` drain at those call sites. Once
the above was fixed, testing showed that drain was never actually
necessary, so it was removed rather than kept as unproven defensive
code.
## 6. License ## 6. License
+26 -2
View File
@@ -319,6 +319,14 @@ void xr_poll_events(void) {
} }
} }
} }
// xrPollEvent() leaves gl4es reporting a stray GL_INVALID_OPERATION on
// every call while the session is running - drain it (looped, since
// more than one can accumulate across xrPollEvent()'s possibly-multiple
// iterations above) so it doesn't surface as a false positive at the
// next unrelated glGetError() check in this app's own rendering code.
while (glGetError() != GL_NO_ERROR) {
}
} }
bool xr_is_session_running(void) { return g_session_running; } bool xr_is_session_running(void) { return g_session_running; }
@@ -434,6 +442,7 @@ void xr_frame_end(void) {
endInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; endInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
endInfo.layerCount = layerCount; endInfo.layerCount = layerCount;
endInfo.layers = layerCount > 0 ? layers : NULL; endInfo.layers = layerCount > 0 ? layers : NULL;
xr_check(xrEndFrame(g_session, &endInfo), "xrEndFrame"); xr_check(xrEndFrame(g_session, &endInfo), "xrEndFrame");
} }
@@ -471,6 +480,21 @@ void xr_get_game_quad_extent(float *distance_m, float *half_width_m, float *half
} }
void xr_get_game_resolution(int *width, int *height) { void xr_get_game_resolution(int *width, int *height) {
*width = g_game_swapchain.width; // Not g_game_swapchain.width/height: that's the fixed physical size of
*height = g_game_swapchain.height; // the swapchain image the game is composited into (see xr_init()) -
// android_draw_surface_as_quad() (OpenGL.cc, engine-patches/11) always
// stretch-blits drawSurface to fill that image regardless of
// drawSurface's own pixel size, so the swapchain's size has no relation
// to the coordinate space MouseX/MouseY actually live in. gScreenWide/
// gScreenHigh (InitMac.h) is drawSurface's current size - the
// resolution the in-game options menu can change at runtime
// (ShockBitmap.c's ChangeScreenSize()) independently of the swapchain,
// which is created once at launch and never resized. Using the fixed
// swapchain size here after such a change mapped the laser pointer's
// quad-space hit into the wrong coordinate space entirely, moving
// MouseX/MouseY out of the engine's actual screen bounds - which is
// what made the cursor disappear and become unclickable.
extern int32_t gScreenWide, gScreenHigh;
*width = gScreenWide;
*height = gScreenHigh;
} }
+7 -5
View File
@@ -74,11 +74,13 @@ void xr_shutdown(void);
// laser pointer always matches whatever's actually visible. // laser pointer always matches whatever's actually visible.
void xr_get_game_quad_extent(float *distance_m, float *half_width_m, float *half_height_m); void xr_get_game_quad_extent(float *distance_m, float *half_width_m, float *half_height_m);
// Pixel resolution of the game quad's own swapchain - i.e. game_width/ // The engine's current screen resolution (gScreenWide/gScreenHigh,
// game_height as passed to xr_init() (grd_cap->w/h, see Shock.c's // InitMac.h) - not the game quad's own swapchain size, which is fixed at
// InitSDL()) - the logical coordinate space xr_input.c needs to convert a // launch and never changes - the logical coordinate space xr_input.c needs
// game-quad ray hit's normalized u,v into engine mouse coordinates (see // to convert a game-quad ray hit's normalized u,v into engine mouse
// xr_mouse.h). // coordinates (see xr_mouse.h). Can change at runtime via the in-game
// options menu (ShockBitmap.c's ChangeScreenSize()), independently of the
// swapchain.
void xr_get_game_resolution(int *width, int *height); void xr_get_game_resolution(int *width, int *height);
#ifdef __cplusplus #ifdef __cplusplus
+1
View File
@@ -149,6 +149,7 @@ bool xr_swapchain_acquire(XrInstance instance, XrSwapchainState *state) {
// (android_draw_surface_as_quad()) land in the right place. // (android_draw_surface_as_quad()) land in the right place.
glBindFramebuffer(GL_FRAMEBUFFER, state->fbos[imageIndex]); glBindFramebuffer(GL_FRAMEBUFFER, state->fbos[imageIndex]);
glViewport(0, 0, state->width, state->height); glViewport(0, 0, state->width, state->height);
return true; return true;
} }
@@ -6,8 +6,6 @@ import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log; import android.util.Log;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
@@ -1,6 +1,6 @@
--- a/src/MacSrc/Shock.c --- a/src/MacSrc/Shock.c
+++ b/src/MacSrc/Shock.c +++ b/src/MacSrc/Shock.c
@@ -162,6 +162,17 @@ @@ -162,9 +162,26 @@
void InitSDL() { void InitSDL() {
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
@@ -12,9 +12,18 @@
+ // src/audio/aaudio/SDL_aaudio.c). SDLSound.c opens two: one directly via + // src/audio/aaudio/SDL_aaudio.c). SDLSound.c opens two: one directly via
+ // SDL_OpenAudioDevice() for cutscene audio, one via Mix_OpenAudio() for + // SDL_OpenAudioDevice() for cutscene audio, one via Mix_OpenAudio() for
+ // SFX/MIDI. The older OpenSL ES backend has no such limitation, so force + // SFX/MIDI. The older OpenSL ES backend has no such limitation, so force
+ // it instead of AAudio. + // it instead of AAudio. Uses SDL_HINT_OVERRIDE priority so nothing set
+ SDL_SetHint(SDL_HINT_AUDIODRIVER, "openslES"); + // earlier (or by SDL/the platform) can take precedence over it.
+ SDL_SetHintWithPriority(SDL_HINT_AUDIODRIVER, "openslES", SDL_HINT_OVERRIDE);
+#endif +#endif
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) < 0) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) < 0) {
DEBUG("%s: Init failed", __FUNCTION__); DEBUG("%s: Init failed", __FUNCTION__);
} }
+#ifdef __ANDROID__
+ // Standing diagnostic: reports which audio driver SDL actually selected,
+ // to confirm the openslES override above took effect on-device.
+ INFO("SDL audio driver: %s", SDL_GetCurrentAudioDriver());
+#endif
// TODO: figure out some universal set of settings that work...
#ifdef __ANDROID__
@@ -1,6 +1,16 @@
--- a/src/MacSrc/ShockBitmap.c --- a/src/MacSrc/ShockBitmap.c
+++ b/src/MacSrc/ShockBitmap.c +++ b/src/MacSrc/ShockBitmap.c
@@ -42,11 +42,26 @@ @@ -27,6 +27,9 @@
#include "Shock.h"
#include "ShockBitmap.h"
#include "2d.h"
+#ifdef __ANDROID__
+#include "OpenGL.h"
+#endif
//--------------------
// Globals
@@ -42,11 +45,26 @@
SDL_RenderClear(renderer); SDL_RenderClear(renderer);
@@ -27,3 +37,32 @@
SDL_RenderSetLogicalSize(renderer, width, height); SDL_RenderSetLogicalSize(renderer, width, height);
@@ -54,6 +72,28 @@
gScreenWide = width;
gScreenHigh = height;
+
+#ifdef __ANDROID__
+ // Skipping SDL_SetWindowSize() above (see the comment for why) also
+ // means no native resize event ever fires on Android when the in-game
+ // resolution changes - opengl_resize() (OpenGL.cc) only ever runs from
+ // that event (see sdl_events.c) or once at OpenGL init, and it's what
+ // (re)creates backupBuffer, the offscreen 3D-view render target, sized
+ // to the logical resolution just changed above. Without this, it stays
+ // stuck at whatever size it was at launch while the rest of the game
+ // (drawSurface/gScreenWide/gScreenHigh, just updated above) moves on to
+ // the new resolution - the actual cause of the display breaking and
+ // becoming unresponsive to further input after changing resolution
+ // in-game. Call it directly instead - after gScreenWide/gScreenHigh
+ // update above, same as the desktop resize event (delivered via
+ // SDL_PollEvent(), so always processed after this function has already
+ // returned) - using the same physical window size init_opengl() itself
+ // used (SDL_GetWindowSize()); that never changes on Android, only the
+ // logical size set above does.
+ int physWidth, physHeight;
+ SDL_GetWindowSize(window, &physWidth, &physHeight);
+ opengl_resize(physWidth, physHeight);
+#endif
}
//------------------------------------------------------------------------------------
@@ -0,0 +1,11 @@
--- a/src/MacSrc/SDLSound.c
+++ b/src/MacSrc/SDLSound.c
@@ -42,7 +42,7 @@
}
if (Mix_OpenAudio(48000, AUDIO_S16SYS, 2, 2048) < 0) {
- ERROR("%s: Couldn't open audio device", __FUNCTION__);
+ ERROR("%s: Couldn't open audio device: %s", __FUNCTION__, SDL_GetError());
}
Mix_AllocateChannels(SND_MAX_SAMPLES);
@@ -0,0 +1,181 @@
--- a/src/MacSrc/SDLSound.c
+++ b/src/MacSrc/SDLSound.c
@@ -19,24 +19,6 @@
// Startup the sound system
- SDL_AudioSpec spec, obtained;
- spec.freq = 48000;
- spec.format = AUDIO_S16SYS;
- spec.channels = 2;
- spec.samples = 2048;
- spec.callback = AudioStreamCallback;
- spec.userdata = (void *)&cutscene_audiostream;
-
- extern SDL_AudioDeviceID device;
- device = SDL_OpenAudioDevice(NULL, 0, &spec, &obtained, 0);
-
- if (device == 0) {
- ERROR("Could not open SDL audio: %s", SDL_GetError());
- } else {
- INFO("Opened Music Stream, deviceID %d, freq %d, size %d, format %d, channels %d, samples %d", device,
- obtained.freq, obtained.size, obtained.format, obtained.channels, obtained.samples);
- }
-
if (Mix_Init(MIX_INIT_MP3) < 0) {
ERROR("%s: Init failed", __FUNCTION__);
}
@@ -50,10 +32,18 @@
Mix_HookMusic(MusicCallback, (void *)&MusicDev);
Mix_VolumeMusic(MIX_MAX_VOLUME); // use max volume for music stream
+ // Cutscene/audiolog playback (AudioStreamCallback, see cutsloop.c) used to
+ // run through its own SDL_OpenAudioDevice() device, separate from this
+ // one - SDL only ever allows one open non-capture device on Android's
+ // audio backends, so that second open always failed with "Audio device
+ // already open" and both SFX and MIDI music (both routed through this
+ // Mix_OpenAudio device) silently never played. Mixed into this device's
+ // post-mix stage instead, so there is only ever one device open.
+ Mix_SetPostMix(AudioStreamCallback, (void *)&cutscene_audiostream);
+
InitReadXMI();
atexit(Mix_CloseAudio);
- atexit(SDL_CloseAudio);
return OK;
}
--- a/src/MacSrc/Shock.c
+++ b/src/MacSrc/Shock.c
@@ -60,8 +60,6 @@
SDL_Palette *sdlPalette;
SDL_Renderer *renderer;
-SDL_AudioDeviceID device;
-
int num_args;
char **arg_values;
--- a/src/GameSrc/cutsloop.c
+++ b/src/GameSrc/cutsloop.c
@@ -83,15 +83,25 @@
extern char FrnSubtitle[256];
extern char GerSubtitle[256];
-extern SDL_AudioDeviceID device;
-
-
+// Runs as SDL_mixer's post-mix hook (see snd_start_digital() in
+// SDLSound.c) rather than a dedicated SDL audio device's callback, so
+// SFX/MIDI music sharing the same device are already mixed into `stream`
+// by the time this runs - mix in rather than overwrite, so they aren't
+// silenced whenever a cutscene or audiolog is playing.
void AudioStreamCallback(void *userdata, unsigned char *stream, int len)
{
+ static Uint8 mixbuf[MOVIE_DEFAULT_BLOCKLEN * 4];
SDL_AudioStream *as = *(SDL_AudioStream **)userdata;
- if (as != NULL && SDL_AudioStreamAvailable(as) > 0)
- SDL_AudioStreamGet(as, stream, len);
+ if (as == NULL || SDL_AudioStreamAvailable(as) <= 0)
+ return;
+
+ if (len > (int)sizeof(mixbuf))
+ len = (int)sizeof(mixbuf);
+
+ int got = SDL_AudioStreamGet(as, mixbuf, len);
+ if (got > 0)
+ SDL_MixAudioFormat(stream, mixbuf, AUDIO_S16SYS, got, SDL_MIX_MAXVOLUME);
}
@@ -153,11 +163,13 @@
if (cutscene_audiostream != NULL)
{
- SDL_PauseAudioDevice(device, 1);
- SDL_Delay(1);
-
+ // AudioStreamCallback (SDL_mixer's post-mix hook) runs continuously on
+ // SDL_mixer's own audio thread and reads this pointer - lock around
+ // freeing it so that thread can't be mid-read when it's freed.
+ SDL_LockAudio();
SDL_FreeAudioStream(cutscene_audiostream);
cutscene_audiostream = NULL;
+ SDL_UnlockAudio();
if (cutscene_audiobuffer)
{
@@ -184,8 +196,6 @@
if (cutscene_audiostream)
{
- SDL_PauseAudioDevice(device, 0);
-
if (cutscene_audiobuffer_size > 0)
{
// === adjust volume in buffer here ===
@@ -325,10 +335,9 @@
if (sfx_on)
{
- SDL_PauseAudioDevice(device, 1);
- SDL_Delay(1);
-
+ SDL_LockAudio();
cutscene_audiostream = SDL_NewAudioStream(AUDIO_U8, 1, fix_int(amovie->a.sampleRate), AUDIO_S16SYS, 2, 48000);
+ SDL_UnlockAudio();
cutscene_audiobuffer_pos = cutscene_audiobuffer;
}
--- a/src/GameSrc/audiolog.c
+++ b/src/GameSrc/audiolog.c
@@ -61,8 +61,6 @@
extern uchar curr_alog_vol;
extern char which_lang;
-extern SDL_AudioDeviceID device;
-
errtype audiolog_init(void) { return OK; }
errtype audiolog_play(int email_id) {
@@ -114,10 +112,9 @@
DEBUG("%s: Playing email", __FUNCTION__);
- SDL_PauseAudioDevice(device, 1);
- SDL_Delay(1);
-
+ SDL_LockAudio();
cutscene_audiostream = SDL_NewAudioStream(AUDIO_U8, 1, fix_int(palog->a.sampleRate), AUDIO_S16SYS, 2, 48000);
+ SDL_UnlockAudio();
audiolog_audiobuffer_pos = audiolog_audiobuffer;
@@ -150,11 +147,14 @@
}
if (cutscene_audiostream != NULL) {
- SDL_PauseAudioDevice(device, 1);
- SDL_Delay(1);
-
+ // AudioStreamCallback (SDL_mixer's post-mix hook, see cutsloop.c)
+ // runs continuously on SDL_mixer's own audio thread and reads this
+ // pointer - lock around freeing it so that thread can't be
+ // mid-read when it's freed.
+ SDL_LockAudio();
SDL_FreeAudioStream(cutscene_audiostream);
cutscene_audiostream = NULL;
+ SDL_UnlockAudio();
if (audiolog_audiobuffer) {
free(audiolog_audiobuffer);
@@ -177,8 +177,6 @@
errtype audiolog_loop_callback(void) {
if (cutscene_audiostream) {
- SDL_PauseAudioDevice(device, 0);
-
if (audiolog_audiobuffer_size > 0) {
int i, vol = curr_alog_vol * 127 / 100; // convert from 0-100 to 0-127
@@ -0,0 +1,17 @@
--- a/src/MusicSrc/MusicDevice.c
+++ b/src/MusicSrc/MusicDevice.c
@@ -569,6 +569,14 @@
// connected
ndev->dev.isOpen = 1;
ndev->dev.outputIndex = outputIndex;
+#else
+ // No native MIDI backend implemented for this platform (e.g. Android) -
+ // report failure so InitDecXMI()'s caller falls back to another device
+ // instead of treating an unopened device as successfully initialized.
+ (void)outputIndex;
+ (void)samplerate;
+ WARN("NativeMidiInit(): native MIDI is not implemented on this platform");
+ return -1;
#endif
// suppress compiler warnings
(void)outputIndex;
@@ -0,0 +1,455 @@
--- a/src/MacSrc/OpenGL.cc
+++ b/src/MacSrc/OpenGL.cc
@@ -391,7 +391,16 @@
}
void opengl_resize(int width, int height) {
+#ifndef __ANDROID__
+ // Skipped on Android - see opengl_swap_and_restore()'s comment below for
+ // why re-asserting an already-current context/surface every call breaks
+ // gl4es's own "current framebuffer" bookkeeping, even though the real
+ // driver binding never actually changes. This turned out to be the
+ // actual source of the persistent per-frame GL_INVALID_OPERATION (1282)
+ // that plagued 3D rendering: this same unconditional call was present at
+ // the top of nearly every function in this file, not just here.
SDL_GL_MakeCurrent(window, context);
+#endif
int logical_width, logical_height;
SDL_RenderGetLogicalSize(renderer, &logical_width, &logical_height);
@@ -443,7 +452,10 @@
}
void opengl_end_frame() {
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
// Done rendering to the frame buffer, reset back to normal
BindFrameBuffer(nullptr);
@@ -470,7 +482,10 @@
static bool nightsight_active() { return WareActive(player_struct.hardwarez_status[HARDWARE_GOGGLE_INFRARED]); }
void opengl_start_frame() {
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
// Start rendering to our frame buffer canvas
BindFrameBuffer(&backupBuffer);
@@ -500,6 +515,130 @@
}
#ifdef __ANDROID__
+// Real per-vertex arrays, not glBegin/glVertexAttrib/glVertex3f immediate
+// mode: glVertexAttrib*() is GLES2's *constant*-attribute API - without an
+// enabled vertex array it sets one value for the whole draw call, not a
+// fresh value per vertex. gl4es's immediate-mode emulation only reproduces
+// desktop GL's per-vertex-capture behavior for attribute 0 (position,
+// driven directly by glVertex3f), not for custom attributes like this
+// shader's texcoords/light. That means every one of this file's
+// glBegin(GL_TRIANGLE_STRIP)-based draws - not just the UI-overlay quad
+// this was first found on, but also opengl_light_tmap() (every textured
+// wall/floor polygon), opengl_bitmap() (sprites) and opengl_draw_poly()
+// (flat/gouraud polygons) via draw_vertex() - draws every vertex with
+// whatever texcoord/light was set for the *last* vertex instead of its
+// own, which is what actually produced flat-colored/untextured world
+// geometry after this patch's first revision only fixed the final
+// screen-composite quad (the one bug this was originally found on).
+//
+// android_imm_begin()/android_imm_vertex()/android_imm_end() below emulate
+// just enough of glBegin(GL_TRIANGLE_STRIP)/glVertexAttrib*()/glVertex3f()
+// immediate mode to replace every such call site on Android, by buffering
+// vertices and flushing them as one real glDrawArrays() call - the engine's
+// other rendering still uses real immediate mode for attribute 0 elsewhere
+// on desktop builds, which depends on it not having an enabled array bound,
+// so android_imm_end() always disables the arrays it enables before
+// returning.
+struct AndroidImmVertex {
+ float x, y, z;
+ float u, v;
+ float light;
+};
+
+// Arbitrarily-sized, but generous: the engine's polygons (room walls/
+// floors, sprites, screen-filling quads) are all small, hand-authored
+// shapes, never anywhere near this many vertices.
+static AndroidImmVertex s_immVerts[64];
+static int s_immVertCount;
+static GLint s_immTcAttrib;
+static GLint s_immLightAttrib;
+
+// tcAttrib < 0 means this draw has no texcoord attribute (flat/gouraud
+// polygons via opengl_draw_poly()) - matches draw_vertex()'s own
+// `tcAttrib >= 0` check below.
+static void android_imm_begin(GLint tcAttrib, GLint lightAttrib) {
+ s_immVertCount = 0;
+ s_immTcAttrib = tcAttrib;
+ s_immLightAttrib = lightAttrib;
+}
+
+static void android_imm_vertex(float x, float y, float z, float u, float v, float light) {
+ if (s_immVertCount >= (int)(sizeof(s_immVerts) / sizeof(s_immVerts[0]))) {
+ WARN("android_imm_vertex: dropping vertex past the %d-vertex limit", s_immVertCount);
+ return;
+ }
+ AndroidImmVertex &vert = s_immVerts[s_immVertCount++];
+ vert.x = x;
+ vert.y = y;
+ vert.z = z;
+ vert.u = u;
+ vert.v = v;
+ vert.light = light;
+}
+
+static void android_imm_end() {
+ if (s_immVertCount < 3)
+ return;
+ glEnableVertexAttribArray(0);
+ if (s_immTcAttrib >= 0)
+ glEnableVertexAttribArray(s_immTcAttrib);
+ glEnableVertexAttribArray(s_immLightAttrib);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex), &s_immVerts[0].x);
+ if (s_immTcAttrib >= 0)
+ glVertexAttribPointer(s_immTcAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex),
+ &s_immVerts[0].u);
+ glVertexAttribPointer(s_immLightAttrib, 1, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex),
+ &s_immVerts[0].light);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, s_immVertCount);
+ glDisableVertexAttribArray(0);
+ if (s_immTcAttrib >= 0)
+ glDisableVertexAttribArray(s_immTcAttrib);
+ glDisableVertexAttribArray(s_immLightAttrib);
+}
+
+// Same underlying bug as android_imm_*() above, for opengl_draw_star():
+// opengl_begin_stars()/opengl_draw_star()/opengl_end_stars() draw up to
+// NUM_STARS (rendtool.c) individual GL_POINTS between one begin/end pair,
+// each with its own per-star brightness via glVertexAttrib1f(lightAttrib,
+// ...)/glVertex3f() immediate mode - under gl4es that made every star on
+// screen render at whatever brightness was set for the *last* one. Shaped
+// differently enough from android_imm_*() above (GL_POINTS instead of
+// GL_TRIANGLE_STRIP, far more vertices, no texcoord) to warrant its own
+// small buffer rather than overloading that one.
+static AndroidImmVertex s_starVerts[1000]; // NUM_STARS, GameSrc/rendtool.c
+static int s_starVertCount;
+static GLint s_starLightAttrib;
+
+static void android_stars_begin(GLint lightAttrib) {
+ s_starVertCount = 0;
+ s_starLightAttrib = lightAttrib;
+}
+
+static void android_stars_vertex(float x, float y, float z, float light) {
+ if (s_starVertCount >= (int)(sizeof(s_starVerts) / sizeof(s_starVerts[0]))) {
+ WARN("android_stars_vertex: dropping star past the %d-star limit", s_starVertCount);
+ return;
+ }
+ AndroidImmVertex &vert = s_starVerts[s_starVertCount++];
+ vert.x = x;
+ vert.y = y;
+ vert.z = z;
+ vert.light = light;
+}
+
+static void android_stars_end() {
+ if (s_starVertCount == 0)
+ return;
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(s_starLightAttrib);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex), &s_starVerts[0].x);
+ glVertexAttribPointer(s_starLightAttrib, 1, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex),
+ &s_starVerts[0].light);
+ glDrawArrays(GL_POINTS, 0, s_starVertCount);
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(s_starLightAttrib);
+}
+
// Shared by opengl_swap_and_restore()'s UI-overlay blit and
// android_composite_software_frame() below - draws an SDL_Surface as a
// fullscreen textured quad via gl4es. SDL's own renderer can't be used
@@ -537,42 +676,15 @@
set_blend_mode(blend);
- // Real per-vertex arrays, not glBegin/glVertexAttrib/glVertex3f
- // immediate mode: glVertexAttrib*() is GLES2's *constant*-attribute
- // API - without an enabled vertex array it sets one value for the
- // whole draw call, not a fresh value per vertex. gl4es's immediate-mode
- // emulation only reproduces desktop GL's per-vertex-capture behavior
- // for attribute 0 (position, driven directly by glVertex3f), not for
- // custom attributes like this shader's texcoords/light.
- //
// V is flipped (1 at the bottom, 0 at the top): GL texture v=0
// addresses the first row of data passed to glTexImage2D, but uiRgba
// (an SDL surface) stores its rows top-down.
- struct QuadVertex {
- float x, y, z;
- float u, v;
- float light;
- };
- static const QuadVertex quadVerts[4] = {
- {1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f},
- {1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f},
- {-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f},
- {-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f},
- };
- glEnableVertexAttribArray(0);
- glEnableVertexAttribArray(tcAttrib);
- glEnableVertexAttribArray(lightAttrib);
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(QuadVertex), &quadVerts[0].x);
- glVertexAttribPointer(tcAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(QuadVertex), &quadVerts[0].u);
- glVertexAttribPointer(lightAttrib, 1, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
- &quadVerts[0].light);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- // The engine's other rendering uses glBegin/glVertexAttrib/glVertex3f
- // immediate mode for attribute 0, which depends on it not having an
- // enabled array bound.
- glDisableVertexAttribArray(0);
- glDisableVertexAttribArray(tcAttrib);
- glDisableVertexAttribArray(lightAttrib);
+ android_imm_begin(tcAttrib, lightAttrib);
+ android_imm_vertex(1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f);
+ android_imm_vertex(1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
+ android_imm_vertex(-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
+ android_imm_vertex(-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+ android_imm_end();
// glFinish(), not glFlush(): xr_swapchain_release() (called by our
// caller right after this returns) hands the image to the OpenXR
// runtime for cross-process consumption, which requires the GPU write
@@ -642,6 +754,16 @@
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+#ifdef __ANDROID__
+ // Real per-vertex arrays, not glBegin/glVertexAttrib/glVertex3f
+ // immediate mode - see android_imm_begin()'s comment above for why.
+ android_imm_begin(tcAttrib, lightAttrib);
+ android_imm_vertex(1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
+ android_imm_vertex(1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f);
+ android_imm_vertex(-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+ android_imm_vertex(-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
+ android_imm_end();
+#else
glBegin(GL_TRIANGLE_STRIP);
glVertexAttrib2f(tcAttrib, 1.0f, 0.0f);
glVertexAttrib1f(lightAttrib, 1.0f);
@@ -656,6 +778,7 @@
glVertexAttrib1f(lightAttrib, 1.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();
+#endif
// Finish drawing the 3d view
glFlush();
@@ -709,7 +832,10 @@
render_width = width;
render_height = height;
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
int lw, lh;
SDL_RenderGetLogicalSize(renderer, &lw, &lh);
@@ -729,7 +855,10 @@
}
static bool opengl_cache_texture(CachedTexture toCache, grs_bitmap *bm) {
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
// GL_UNPACK_ROW_LENGTH must describe the buffer actually passed to
// glTexImage2D() below (toCache.converted->pixels, a separately
@@ -771,7 +900,10 @@
DEBUG("Clearing OpenGL texture cache.");
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
for (auto iter = texturesByBitsPtr.begin(); iter != texturesByBitsPtr.end();) {
// don't free locked surfaces
@@ -896,10 +1028,16 @@
light = 1.0f - (clut - grd_screen->ltab) / 4096.0f;
}
+#ifdef __ANDROID__
+ (void)tcAttrib; // captured by android_imm_begin() instead - see its comment above
+ android_imm_vertex(vertex.x / 65536.0f, vertex.y / 65536.0f, -vertex.z / 65536.0f,
+ vertex.uv.u / 256.0f, vertex.uv.v / 256.0f, light);
+#else
if (tcAttrib >= 0)
glVertexAttrib2f(tcAttrib, vertex.uv.u / 256.0, vertex.uv.v / 256.0);
glVertexAttrib1f(lightAttrib, light);
glVertex3f(vertex.x / 65536.0f, vertex.y / 65536.0f, -vertex.z / 65536.0f);
+#endif
}
int opengl_draw_tmap(int n, g3s_phandle *vp, grs_bitmap *bm) { return opengl_light_tmap(n, vp, bm); }
@@ -910,7 +1048,10 @@
return CLIP_ALL;
}
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
set_blend_mode(bm->flags & BMF_TRANS);
glUseProgram(textureShaderProgram.shaderProgram);
@@ -928,13 +1069,21 @@
glUniformMatrix4fv(textureShaderProgram.uniView, 1, false, ViewMatrix);
glUniformMatrix4fv(textureShaderProgram.uniProj, 1, false, ProjectionMatrix);
+#ifdef __ANDROID__
+ android_imm_begin(tcAttrib, lightAttrib);
+#else
glBegin(GL_TRIANGLE_STRIP);
+#endif
draw_vertex(*(vp[1]), tcAttrib, lightAttrib);
draw_vertex(*(vp[0]), tcAttrib, lightAttrib);
draw_vertex(*(vp[2]), tcAttrib, lightAttrib);
if (n > 3)
draw_vertex(*(vp[3]), tcAttrib, lightAttrib);
+#ifdef __ANDROID__
+ android_imm_end();
+#else
glEnd();
+#endif
return CLIP_NONE;
}
@@ -949,7 +1098,10 @@
return CLIP_ALL;
}
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
set_blend_mode(bm->flags & BMF_TRANS);
glUseProgram(textureShaderProgram.shaderProgram);
@@ -976,6 +1128,14 @@
glUniform1i(textureShaderProgram.uniMutant, (bm->type == BMT_TLUC8) || (bm->flags == BMF_TLUC8));
+#ifdef __ANDROID__
+ android_imm_begin(tcAttrib, lightAttrib);
+ android_imm_vertex(convx(vpl[1]->x), convy(vpl[1]->y), 0.0f, 1.0f, 0.0f, light);
+ android_imm_vertex(convx(vpl[0]->x), convy(vpl[0]->y), 0.0f, 0.0f, 0.0f, light);
+ android_imm_vertex(convx(vpl[2]->x), convy(vpl[2]->y), 0.0f, 1.0f, 1.0f, light);
+ android_imm_vertex(convx(vpl[3]->x), convy(vpl[3]->y), 0.0f, 0.0f, 1.0f, light);
+ android_imm_end();
+#else
glBegin(GL_TRIANGLE_STRIP);
glVertexAttrib2f(tcAttrib, 1.0f, 0.0f);
glVertexAttrib1f(lightAttrib, light);
@@ -990,6 +1150,7 @@
glVertexAttrib1f(lightAttrib, light);
glVertex3f(convx(vpl[3]->x), convy(vpl[3]->y), 0.0f);
glEnd();
+#endif
glUniform1i(textureShaderProgram.uniMutant, false);
@@ -1007,7 +1168,10 @@
return CLIP_ALL;
}
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
glUseProgram(colorShaderProgram.shaderProgram);
glUniformMatrix4fv(colorShaderProgram.uniView, 1, false, ViewMatrix);
@@ -1064,7 +1228,11 @@
GLint lightAttrib = colorShaderProgram.lightAttrib;
long a = 0, b = n_verts - 1;
+#ifdef __ANDROID__
+ android_imm_begin(-1, lightAttrib);
+#else
glBegin(GL_TRIANGLE_STRIP);
+#endif
while (true) {
draw_vertex(*(p[a]), -1, lightAttrib);
if (a++ == b)
@@ -1073,7 +1241,11 @@
if (b-- == a)
break;
}
+#ifdef __ANDROID__
+ android_imm_end();
+#else
glEnd();
+#endif
return CLIP_NONE;
}
@@ -1085,7 +1257,10 @@
}
void opengl_begin_stars() {
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
+#endif
// Bind the star shader before setting the point size: on Android,
// questshock_glPointSize() (see the __ANDROID__ block above) needs
@@ -1108,11 +1283,19 @@
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+#ifdef __ANDROID__
+ android_stars_begin(starShaderProgram.lightAttrib);
+#else
glBegin(GL_POINTS);
+#endif
}
void opengl_end_stars() {
+#ifdef __ANDROID__
+ android_stars_end();
+#else
glEnd();
+#endif
// Turn off the stencil test and blending
opengl_set_stencil(0x00);
@@ -1120,9 +1303,11 @@
}
int opengl_draw_star(fix star_x, fix star_y, int c, bool anti_alias) {
+#ifndef __ANDROID__
+ // See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
-
GLint lightAttrib = starShaderProgram.lightAttrib;
+#endif
float x = fix_float(star_x);
float y = fix_float(star_y);
@@ -1142,8 +1327,12 @@
int color = (std_color_base + std_color_range - 1 - c);
color = (255 * color) / (std_color_range + 1);
+#ifdef __ANDROID__
+ android_stars_vertex(x, y, -0.25f, color / 255.0f);
+#else
glVertexAttrib1f(lightAttrib, color / 255.0f);
glVertex3f(x, y, -0.25f);
+#endif
return CLIP_NONE;
}
@@ -0,0 +1,45 @@
--- a/src/GameSrc/frsetup.c
+++ b/src/GameSrc/frsetup.c
@@ -635,7 +635,27 @@
uchar old_cam_type;
int detail;
+#ifdef __ANDROID__
+ // should_opengl_swap() (unlike use_opengl(), see its own
+ // FR_PICKUPM_MASK|FR_HACKCAM_MASK exclusion just below and the
+ // matching one used for the STEREO_SUPPORT path in fr_send_view()
+ // below) doesn't know which _fr context is currently being prepared -
+ // it's a frame-level check, also used by Shock.c's SDLDraw() to pick
+ // the frame's overall compositing path. render_hack_cameras() (see
+ // render.c) calls fr_rend() - and so this function - a second time per
+ // real frame for each active hack-camera monitor, rendering into a
+ // private software (FR_DOUBLEB_MASK) canvas rather than the actual
+ // GL-backed screen. Without this exclusion, opengl_start_frame() runs
+ // again mid-frame for that render even though its own polygon draw
+ // calls correctly stay on the software path (use_opengl() already
+ // excludes them) - re-entering the OpenXR swapchain/framebuffer setup
+ // a second time before the first call's frame has ended. Not known to
+ // be exercised on desktop (no OpenXR swapchain there), so this fix is
+ // kept Android-only rather than changing should_opengl_swap() itself.
+ if (should_opengl_swap() && !(_fr_curflags & (FR_PICKUPM_MASK | FR_HACKCAM_MASK))) {
+#else
if(should_opengl_swap()) {
+#endif
opengl_start_frame();
}
@@ -823,7 +843,14 @@
g3_end_frame();
+#ifdef __ANDROID__
+ // Mirrors the same fix in fr_start_view() above - see its comment.
+ // opengl_start_frame()/opengl_end_frame() must stay paired per real
+ // GL frame, so this exclusion has to match that one exactly.
+ if (should_opengl_swap() && !(_fr_curflags & (FR_PICKUPM_MASK | FR_HACKCAM_MASK))) {
+#else
if(should_opengl_swap()) {
+#endif
opengl_end_frame();
}
@@ -0,0 +1,34 @@
--- a/src/MacSrc/OpenGL.cc
+++ b/src/MacSrc/OpenGL.cc
@@ -455,10 +455,30 @@
#ifndef __ANDROID__
// See opengl_resize()'s comment above.
SDL_GL_MakeCurrent(window, context);
-#endif
// Done rendering to the frame buffer, reset back to normal
BindFrameBuffer(nullptr);
+#else
+ // BindFrameBuffer(nullptr) (glBindFramebuffer/glBindRenderbuffer to id
+ // 0) means "the real window surface" - on desktop that's SDL's actual
+ // GL window, but this app's real presentation target is never that
+ // SDLSurface's own EGL surface at all - it's the OpenXR swapchain's
+ // per-image FBOs, set up separately in xr_swapchain.c. Whatever
+ // framebuffer/renderbuffer 0 actually resolves to for gl4es in this
+ // configuration, binding it here reliably left gl4es reporting a
+ // persistent GL_INVALID_OPERATION (1282) - confirmed by bisecting
+ // through this whole render path with per-call checkpoints, all clean
+ // right up until "after opengl_end_frame", the one immediately after
+ // this exact call.
+ //
+ // The bind is also functionally dead on Android regardless of that:
+ // by the time anything next draws, Shock.c's SDLDraw() has already
+ // run xr_frame_begin() -> xr_swapchain_acquire(), which rebinds to
+ // that frame's real swapchain FBO before opengl_swap_and_restore()
+ // (the only thing that draws before the next opengl_start_frame()'s
+ // own BindFrameBuffer(&backupBuffer)) runs - so skipping it changes
+ // nothing about what actually gets rendered where.
+#endif
palette_dirty = false;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB