Repository navigation
Releases: Sampreeth004/nfs-carbon-recomp
Release list
0.3.12 - new launcher, settings and controls editor
A redesigned launcher, settings and touch-controls editor, plus the renderer, CPU and audio work since 0.3.5.
New launcher
- One big PLAY button. Without game data it becomes SET UP and goes straight to the Game data page.
- Status tiles for game data, renderer, GPU driver, performance and controls. Tap one to open its settings.
- Quick settings: performance preset, frame rate cap, touch controls, fps counter.
- Works in landscape (side by side) and portrait (stacked).
Settings
Side navigation with Performance, Display, Controls, GPU driver, Game data, Advanced, About. Options are one-tap choices instead of drop-down lists; Xenos-only options are greyed out unless that renderer is selected. Advanced has the new renderer switches (see below) and a reset that keeps your game data, driver and touch layouts.
Controls editor
Full screen at your phone's real proportions, with the overlay's real size and opacity. Drag to move, pinch to resize, snap to grid with centre guides, undo, reset per layout. Tap a control to change its size or the button it presses (driving labels like NOS and E-BRAKE follow the mapping). Overall size, opacity and stick deadzone are in the toolbar.
Renderer (0.3.12)
- Detailed profiling is off by default (Advanced > Detailed profiling): fewer timer calls per draw, no GPU timestamp queries. The fps counter and 5 s stats lines still work.
- Clears touch only the cleared rectangle with a clear load operation, instead of loading and storing the whole (often much larger) render target. Advanced > Clear with load operations turns it off.
- Image barriers wait only for the work that used each image, and passes that reuse a render target now get a proper dependency. Advanced > Precise GPU barriers turns it off.
- These are not yet measured in a controlled scene. If you see flicker, stale areas or garbage in mirrors, shadows or glow, turn the matching switch off and report it.
Since 0.3.5
- 0.3.6–0.3.7 Phone widescreen (wider camera, corrected HUD); the 16:9 view always keeps its proportions.
- 0.3.8 Fixed SDL's Android event loop spinning a CPU core; audio underrun counters without log spam.
- 0.3.9 Batched shader-constant writes; full-screen resolves skip loading old pixels.
- 0.3.10 Much faster texture decoding (NEON copies), no duplicate frame limiter under vsync, MainThread may use the fast cores.
- 0.3.11 Minimap moved to the upper left on phones.
Full details in CHANGELOG.md. Debug APK (arm64-v8a and x86_64). Bring your own game data.
0.3.5 - threaded command processing
- Command fetch thread (
carbon_gpu_threaded_cp, Settings > Graphics > Threaded command processing,
default on). A new "GPU Fetch" thread does what the hardware command fetcher does: it frames the PM4
packets in the ring, expands indirect buffers, drops predicated-off packets, copies the rest into an
8 MiB private queue and publishes the ring read pointer straight away. "GPU Commands" then executes the
queued packets in order with the same code as before, so fences, interrupts, register state and
WAIT_REG_MEM behave exactly as in single-thread mode. The game no longer waits for ring space while
draws are being recorded. New log line every 5 s: peak queue use and how often fetch waited.
Not measured on the phone yet.
0.3.4 - car reflections off
- Car reflections default to Off (
carbon_gpu_reflection_faces = 0, Settings > Graphics > Car reflections). The
six cube-face passes and their resolves were measured as never sampled by any draw, so nothing visible
changes. The old default (2 faces per frame) still cost about 60 draws and 2.4 passes per frame:
on the phone, CPU draw recording 5.6 -> 4.7 ms per frame, GPU 7.25 -> 7.0 ms, GPU p99 15.5 -> 14.4 ms.
Existing installs are moved to Off once; the other options remain and only add work.
0.3.3 - turnip drivers, new settings, redundant resolve skipping
-
Redundant resolves are skipped (
carbon_gpu_skip_redundant_resolves, default on). Every render target
carries a write stamp that changes on each recorded draw, each clear and at creation; each resolved
texture remembers what its last resolves copied (source target, stamp, rectangle, exp bias, swap).
A resolve with an identical signature into a region nothing has overwritten since copies the same
texels, so it is skipped; any resolve into an overlapping region replaces the old signature. Barriers
and layouts are untouched because skipped resolves record no commands.
On the phone (Adreno 830, turnip, driving) this removes about 1.2-2 of ~19 resolves per frame (about
5% of resolve pixels and bandwidth). Resolves cost only about 1.1 ms of ~7.5 ms GPU per frame, so the
effect on frame time is below run-to-run noise. -
New stats lines (
carbon_gpu_stats): frame and GPU time p50/p95/p99, resolves copied/skipped per frame,
resolved pixels and bandwidth, and resolves that were overwritten before anything sampled them, with
the top destinations. -
Finding: about 23% of resolve pixels (7 of ~18 resolves per frame) are overwritten before any draw
samples them: the scene and bloom targets (1-2 of 3 per frame each) and all car-reflection cube faces
(256x256, never sampled because cube fetches do not read resolved faces). They cannot be dropped
soundly without knowing the future of the command stream, so they are still copied. -
Turnip / Adreno driver packages now work. Zips downloaded from the internet (a
meta.json
pluslibvulkan_freedreno.so) used to be rejected because they are ICD drivers, not a full
libvulkan.so. They are now opened through
libadrenotools (BSD-2-Clause, Billy Laws; vendored in
third_party/libadrenotools), which loads the driver beside the system Vulkan loader. Adreno GPUs on
arm64 phones only; if the driver cannot be opened the game uses the system driver. Packages whose
files sit inside a single folder are found too. Fulllibvulkan.soloaders still work as before. -
Thermal auto-downgrade removed (added in 0.3.2): the app no longer polls the thermal headroom or
lowers the fps cap on its own, and the genericGameBridge.setCvarJNI setter it needed is gone. The
frame rate cap is whatever you set in Settings. The ADPF performance hint and the per-frame
re-read of the cap stay. -
Settings redesign: header with a Back button and category tabs (Graphics, GPU driver, Game data,
Controls, About) over dark glass cards in the launcher's style, instead of one long plain list.
Graphics gains one-tap presets (Battery saver, Balanced, Quality) and groups options into Display,
Effects and quality, and Xenos-only cards, each with a short explanation; toggles are switches. The
GPU driver page shows the active driver and its status, lists drivers as tappable rows with
per-driver Remove, and has a single Import button. Settings values and storage are unchanged. -
SDK patch
patches/rexglue-turnip-icd.patch(DynamicLibrary::Adopt,vulkan_icd_drivercvar, ICD
hook). The APK must shiplibmain_hook.so,libhook_impl.so,libfile_redirect_hook.soand
libgsl_alloc_hook.soinjniLibs/arm64-v8a(built by thenfscarbontarget).
GPU driver note: downloaded turnip packages need a Qualcomm Adreno GPU on a 64-bit ARM phone. The APK is debug-signed.
0.3.2 - ADPF and thermal auto-downgrade
- ADPF performance hint (Android 13+ devices): the rendering thread reports
its actual frame time to Android's scheduler each frame. The OS can then lower
clock speeds when frames are easy and raise them when they are not, instead of
running maximum clocks all the time. Expected result: less heat, similar fps.
Loaded viadlopenso the APK runs unchanged on older Android versions. - Thermal auto-downgrade:
PowerManager.getThermalHeadroomis polled every
2.5 s while playing. If the phone is heading toward throttling (headroom ≤ 0.5)
the fps cap drops to 45; at critically hot (≤ 0.15) it drops to 30. When the
phone cools back down (headroom > 0.85) the cap is restored to the setting in
Settings. A brief toast shows the change. This keeps frames smooth instead of
the phone applying an uncontrolled throttle. - Renderer fps cap re-read each frame so any runtime cvar change takes effect
immediately (needed for thermal downgrade; also allows hot-changing the cap
from the TOML without restarting). - Generic JNI cvar setter (
GameBridge.setCvar) lets Java code change any
renderer setting at runtime.
0.3.1 - Pipeline warm-up and arrow steering
- Pipeline warm-up: every pipeline the game uses is recorded to
<title>.pipelinesnext to the
pipeline cache, with its shaders named by their SPIR-V cache key. On the next start they are all
queued to the pipeline workers before the first frame, so scenes seen before no longer stall or
pop in the first time they appear (PC: 78 pipelines queued in 5 ms). This is the same idea as the
reference port's saved pipeline list. No game shader data ships with the app; the list is built
on the player's own device. - Pipeline cache saved while playing: written in the background (throttled) and when the app
is paused, atomically (.tmp+ rename). Before, it was written only at a clean exit or every 64
new pipelines, so on Android, where the app is usually killed, it was almost never kept. - Shader modules shared: identical translated shaders reuse one Vulkan module.
- Touch steering: the steering slider is replaced by left and right arrow buttons. Steering
ramps in while an arrow is held, and you can slide a finger from one arrow to the other.
0.3.0 - 60 fps on the phone, new audio and touch controls
Android APK (debug-signed, arm64-v8a and x86_64) with the native Vulkan renderer. You need your own copy of the game data (ISO or extracted folder); none is included.
Tip for weaker or hotter phones: Settings > Graphics > Render resolution 75% and Frame rate cap 30.
FPS and heat (Snapdragon 8 Elite / Adreno 830, in a race)
| 0.2.0 | 0.3.0 | |
|---|---|---|
| Frame rate | 20-40 fps, swinging, slow-motion feel | 56-60 fps (steady scenes 60) |
| GPU time per frame | 34-49 ms | about 8 ms |
| Renderer CPU per frame | 17-19 ms | about 9 ms |
| Draws recorded per frame | about 2,500-3,000 | about 1,800-2,200 (+ ~600 throttled) |
What made the difference, largest first:
- Bloom off by default on Android (Settings: Bloom). Its blur chain (6-8 full-screen passes of
scattered texture reads) cost 23-38 ms of GPU per frame while driving, for a subtle glow. - Pass render area: passes cover only the rows the game resolves from a target; oversized
targets (640x3728 for a 640x360 buffer) no longer cost a full load and store per pass. - Car reflections and mirror on a budget (Settings: Car reflections, Rear-view mirror at half
rate; Android defaults 2 cube faces per frame and half rate). Throttled passes skip their draws and
their resolve, so the textures keep the previous update. About 600 fewer draws per frame. - Draw recording: redundant pipeline/viewport/scissor/stencil/descriptor calls skipped, texture
descriptor sets shared between draws, single-pass index conversion, no per-draw allocations, and
only the parts of the per-draw constants a shader reads are written. - Game code (Android):
tools/direct_calls.pyturns 142,521 calls between unhooked game
functions (and the PowerPC register save/restore helpers) into direct calls that can be inlined;
built with ThinLTO and-march=armv8.2-a. Run the script after every codegen. - UI repaint: the always-alive achievement toast kept the ImGui UI repainting at every display
refresh (~13% of the app's CPU). Newachievement_toastssetting, off on Android
(patches/rexglue-achievement-toasts.patchfor the SDK'srex_app.cpp). - Async pipelines: new pipelines are built on worker threads; draws needing one are skipped for
a frame or two instead of the frame stalling (carbon_gpu_async_pipelines). - Frame rate cap (Settings: 30/45/60/unlimited), a 60 Hz display request for the game window,
and the renderer and audio park while the app is in the background. - Render resolution (Settings: 100/85/75/60/50%,
carbon_gpu_render_scale) for weaker GPUs:
render targets and resolved images are scaled, everything the game sees stays in guest pixels. - Texture filtering: anisotropy capped (4x on Android) and off for render-to-texture sources.
- Vertex arena and LRU texture/render-target eviction (from 0.2.0 work) keep memory bounded.
- Diagnostics: per-pass GPU timings with shader hashes, pacing stats, a slow-frame report
(draw time, texture uploads, pipelines, waits on the game),carbon_gpu_debug_cycle.
Audio
- Android audio goes through a new AAudio stereo driver (
src/android_aaudio.*): the phone reports
its speaker as 6 channels and the old 5.1 path lost the center channel, where the dialogue is. The
driver folds 5.1 to stereo itself with dialogue at full level and a limiter, uses a lock-free
ring, and reopens on headphone/route changes. - The runtime is built with Xenia Canary/Edge XMA decoder fixes (packets crossing input buffers, the
frame tail left undelivered, headers split across packets, loop starts), which stopped voices
stalling. These are applied to the local SDK tree, not part of this repository.
Touch controls
- Reworked from scratch: controls are drawn from shapes (old PNG icons removed), dark glass style
with a cyan highlight, pedals that fill with pressure, a steering pad with chevrons. - Driving layout follows Carbon's Xbox 360 defaults and drops what is not needed while driving:
steering pad, GAS/BRAKE pedals, E-BRAKE (A), NOS (B), BREAKER (X), CREW (Y), RESET (LB), VIEW (RB),
EVENT (Back) and pause. Gamepad layout keeps every button for menus.
Fixed
- Crash at start on Android: the installed SDK headers were older than the packaged runtime.
- Colors: red/blue swap on the presented frame and double gamma on
k_8_8_8_8_GAMMAtargets.
0.2.0 - native Vulkan renderer, new launcher and controls
Android APK (debug-signed, arm64-v8a and x86_64) with the native Vulkan renderer. You need your own copy of the game data (ISO or extracted folder); none is included.
A new GPU plugin, renderer/, that draws the game with Vulkan directly instead of emulating the
Xenos GPU. It loads through the SDK's existing plugin seam (gpu_plugin = "carbon"), links only
rexruntime and glslang, and builds from the same sources as rexgpu-carbon.dll (Windows) and
librexgpu-carbon.so (Android). The Xenos plugin is not loaded when it is selected. It is written
from scratch; the Xenos register, microcode and texture-layout definitions come from the SDK's
BSD-licensed headers and Xenia documentation. No code from nfsmw-android is used.
Added
renderer/src/command_processor.*: own PM4 ring reader and register mirror. Keeps the contract
the game's Direct3D relies on: read-pointer write-back while the ring drains, interrupts,
scratch/fence writes (MEM_WRITE,EVENT_WRITE_*,REG_TO_MEM,COND_WRITE),WAIT_REG_MEM,
predicated packets (bin mask/select), gamma ramp registers, occlusion query faking. Exports
nfsmw_cp_rptr_seqsoring_wait_hook.cppcan sleep on it as with Xenos.renderer/src/gpu_system.*:IGraphicsSystemimplementation: Vulkan provider and presenter,
MMIO at0x7FC80000, 60 Hz vblank thread, interrupt dispatch, a 3-second watchdog log line.renderer/src/shader_translator.*: Xenos microcode -> GLSL 4.50. Covers all ALU vector/scalar
opcodes (Direct3D 90 * x = 0multiply rule, predicates,a0/aLrelative addressing,
kill), vertex fetch of every vertex format (read from a storage buffer with in-shader endian
swap), texture fetch (1D/2D/3D/stacked/cube, register LOD and gradients, LOD queries), exports,
and control flow (straight-line code, or aswitch (pc)state machine for jumps, calls and
loops). Variants per draw: interpolator count, flat shading mask, pixel parameter generation,
rectangle lists expanded in the vertex shader (no geometry shader needed).renderer/src/shader_compiler.*: glslang GLSL -> SPIR-V with an on-disk cache
(<cache>/carbon_gpu/spirv/).renderer/src/renderer.*: frames in flight (3), per-frame upload chunks (vertices, indices,
constants), dynamic rendering, pipeline cache keyed by full draw state and saved to disk,
index conversion for quad lists, triangle fans and line loops, primitive restart, viewport and
window-offset transform folded into the vertex shader, alpha test, blend/depth/stencil/polygon
offset state, presentation through the SDK presenter with the guest gamma ramp.renderer/src/render_targets.cpp: real Vulkan images per (EDRAM base, pitch, format) instead
of EDRAM emulation; render targets grow on demand. Resolves copy a rectangle into the image that
stands for the destination texture (format conversion, exponent bias, red/blue swap, depth to
R32F), then apply color/depth clears.renderer/src/textures.cpp,texture_decode.*: texture cache keyed by guest layout, untiling
(2D/3D), packed mip tails, endian swap, host format mapping (BC formats native or decoded on the
CPU when unsupported), re-upload when the guest memory fingerprint changes (checked less often
for stable textures), guest swizzles as image view swizzles, signed formats, samplers.- Diagnostics cvars:
carbon_gpu_stats(fps, draws, passes, resolves, uploads and skipped-draw
reasons every 5 s),carbon_gpu_trace_frame=N(logs every pass, draw and resolve of frame N),
carbon_gpu_dump_shaders,carbon_gpu_watchdog,carbon_gpu_vsync,
carbon_gpu_upload_chunk_mb,carbon_gpu_dump_frame_interval=N(saves the presented frame to
carbon_frames/frame_<n>.bmpevery N frames, read back from the GPU, so it works with the
window in the background). Late-registered plugin cvars are set with environment variables
(REX_CARBON_GPU_TRACE_FRAME=900) or the TOML;--name=valueon the command line does not
reach them. - CMake preset
win-amd64-vk: Windows build against the patched SDK built with
REXGLUE_USE_VULKAN=ON(installed totools/rexglue-sdk-win-vk; the prebuilt Windows SDK is
D3D12-only). patches/rexglue-windows-vulkan.patch: guards the POSIX-only census code in the SDK's Vulkan
command processor so the patched SDK (and its Xenos plugin, for A/B) builds on Windows.
Changed
nfscarbon_app.h:gpu_plugindefaults tocarbonon Windows and Android when not set in the config.
The Android settings screen has a renderer switch (Native Vulkan or Xenos fallback).CMakeLists.txt: buildsrenderer/(optionNFSCARBON_BUILD_CARBON_GPU, on by default).
Android app
- New app icon (adaptive icon on Android 8+).
- New launcher: dark racing theme, two-pane landscape layout, status cards (game data, renderer, GPU driver,
display, controls) with ready/attention dots, a PLAY button that becomes SET UP until game data is chosen, and
a CONTROLS shortcut that opens the touch layout editor directly. - Touch controls redraw: colored A/B/X/Y buttons, pill-shaped bumpers/triggers/start/back (larger touch areas),
darker backing for readability on bright scenes, d-pad arrows, ringed analog sticks, bold labels. - New default layouts (version 4, replaces saved layouts once): gamepad with thumbsticks and the d-pad
at the bottom, driving with a wide steering strip on the left, brake/gas on the right and the action buttons
in the middle. Driving is the default layout.
Performance (Android prep)
- Persistent vertex arena (
carbon_gpu_vertex_arena_mb, default 128): guest vertex buffers are copied once and
re-checked by 16 KiB page hash (less often while stable) instead of being re-uploaded every frame. Only the
vertices the draw's indices reach are copied (carbon_gpu_vertex_ranges). Rewritten buffers get a fresh copy
(frames in flight keep the old one); buffers that change 3 times fall back to the per-frame upload. In game the
vertex copy went from about 42 MiB to about 0 MiB per frame. - Resource eviction: textures are freed least recently used first when over
carbon_gpu_texture_budget_mb
(3072 on PC, 640 on Android) and after 30 s unused; render targets after 15 s unused. Memory use is in the stats line.
Fixed (colors)
- Frames resolved with the Xenos red/blue swap flag showed swapped colors on screen (gold instead of cyan logo).
The present step now undoes the swap.
Fixed
- Wrong colors on
k_8_8_8_8_GAMMArender targets: the format was mapped to
VK_FORMAT_R8G8B8A8_SRGB, causing Vulkan to apply sRGB gamma encoding on
every color write. The_GAMMAsuffix is documentation only — the hardware
stores raw values. Changed toVK_FORMAT_R8G8B8A8_UNORM. - Predicated tiling: the scene is rendered in 256-row tiles, each resolved to the address of its
first row inside one texture. Resolves that land inside an existing resolved texture of the same
format and pitch (on a 32-row strip boundary) now write into it at that row, so the whole frame
reaches the screen instead of the top tile. - Render target sizes: heights came from the 8192 default scissor. They now come from the
scissor and viewport, capped by where the next bound target starts in EDRAM. Formats that only
differ in blending precision (2_10_10_10_FLOAT/_AS_16_16_16_16,2_10_10_10/
_AS_10_10_10_10) share one render target. Render passes per frame went from about 116 to 48. - Boot movies and logos were a flat color:
SQ_INTERPOLATOR_CNTL.param_shadewas used as a flat
shading mask, which made the movie texture coordinates constant. It is ignored now (everything
is interpolated smoothly, as in the SDK's Xenos backends).
Status (PC, RTX 5070, 2026-10-06)
- Boot movies, title screen, loading screen, front end, the prologue drive with HUD and mirror,
and the pause menu (blurred background) render correctly at 60 fps with no Xenos code; up to
about 4,000 draws, 56 passes and 23 resolves per frame in game. - 48 single-point draws per frame with color writes masked off and no depth/stencil are skipped
(statno-output); they write nothing. - Open:
- The Android build has not been profiled on a device with the optimizations yet; expect to tune the
texture budget and vertex arena size per device. - Resolves are full-screen copy passes, which are relatively costly on tile-based mobile GPUs.
- The APK is debug-signed.
- The Android build has not been profiled on a device with the optimizations yet; expect to tune the







