Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ add_subdirectory(adrenotools)
add_library(winlator SHARED
winlator/drawable.c
winlator/gpu_image.c
winlator/surface_compositor.c
winlator/sysvshared_memory.c
winlator/xconnector_epoll.c
winlator/alsa_client.c
Expand Down
633 changes: 633 additions & 0 deletions app/src/main/cpp/winlator/surface_compositor.c

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions app/src/main/feature/library/GameSettings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ class GameSettingsStateHolder {
val dxWrapperEntries = mutableStateOf<List<String>>(emptyList())
val selectedDxWrapper = mutableIntStateOf(0)

// Per-container "Direct Composition" toggle. When true and the device exposes
// SurfaceControl (API 29+, see SurfaceCompositor.isAvailable()), fullscreen
// direct-scanout drawables are routed to a sibling Android SurfaceControl
// layer (HWC/DPU overlay plane) instead of being composited by the in-process
// GLRenderer. The toggle is sampled at activity startup and held for the
// session — only takes effect on the next launch of the container.
val directComposition = mutableStateOf(false)

// Graphics Driver Configuration (inline card)
val gfxConfigExpanded = mutableStateOf(false)
val gfxVulkanVersionEntries = mutableStateOf<List<String>>(emptyList())
Expand Down Expand Up @@ -3219,6 +3227,18 @@ private fun AdvancedSection(
checked = state.fullscreenStretched.value,
onCheckedChange = { state.fullscreenStretched.value = it }
)

// Direct Composition is currently a container-level toggle (sampled at
// activity startup, held for the session). Hide from shortcut-edit
// sheets to avoid suggesting it can be flipped per-launch.
if (state.isContainerEditMode.value) {
Spacer(Modifier.height(SettingItemGap))
SettingCheckbox(
label = stringResource(R.string.session_display_direct_composition),
checked = state.directComposition.value,
onCheckedChange = { state.directComposition.value = it }
)
}
}

Spacer(Modifier.height(SettingSectionGap))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor(
}

state.fullscreenStretched.value = c?.isFullscreenStretched() ?: false
state.directComposition.value = c?.isDirectCompositionEnabled() ?: false

// Steam fields are shortcut-only in the UI; leave any existing steam
// state on the container untouched — saveSettings() skips them.
Expand Down Expand Up @@ -753,6 +754,7 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor(
c.setWinComponents(wincomponents)
c.setDrives(drivesString)
c.setFullscreenStretched(state.fullscreenStretched.value)
c.setDirectCompositionEnabled(state.directComposition.value)
c.setInputType(finalInputType)
c.setStartupSelection(startupSelection)
c.setBox64Version(box64Version)
Expand Down Expand Up @@ -805,6 +807,13 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor(
manager.createContainerAsync(data, contentsManager) { newContainer ->
if (newContainer != null) {
saveMouseWarpOverride(newContainer)
// Persist Direct Composition on the new container — it's stored as
// an extraData entry, not a top-level JSON field, so it doesn't
// ride along in the `data` map handed to createContainerAsync.
if (state.directComposition.value) {
newContainer.setDirectCompositionEnabled(true)
newContainer.saveData()
}
} else {
WinToast.show(context, R.string.setup_wizard_unable_to_install_system_files)
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,8 @@ E.g. <b>META</b> for <i>META key</i>, \n
<string name="session_display_frame_rate">Frame Rate</string>
<string name="session_display_title">Display</string>
<string name="session_display_fullscreen_stretched">Enable Fullscreen (Stretched)</string>
<string name="session_display_direct_composition">Direct Composition (experimental)</string>
<string name="session_display_direct_composition_summary">Route fullscreen game frames straight to an Android SurfaceControl layer (HWC/DPU overlay) instead of compositing them in-process. Lower latency and battery use when supported. Applies on next launch.</string>
<string name="session_display_fps_label">FPS:</string>
<string name="session_display_renderer_label">Renderer:</string>
<string name="session_display_gpu_label">GPU:</string>
Expand Down
26 changes: 26 additions & 0 deletions app/src/main/runtime/container/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ public class Container {
public static final String STEAM_TYPE_LIGHT = "light";
public static final String STEAM_TYPE_ULTRALIGHT = "ultralight";

/**
* extraData JSON key for the per-container "Direct Composition" toggle.
* Stored as a string ("1"/"0") for symmetry with the rest of extraData. The
* setting is read at activity startup and applies for the whole session — it
* controls whether fullscreen drawables are pushed to a sibling Android
* SurfaceControl layer (zero-copy DPU scanout) instead of being composited
* by the in-process GLRenderer. Changing it mid-game is not supported.
*/
public static final String EXTRA_DIRECT_COMPOSITION = "directComposition";

private ContainerManager containerManager;


Expand Down Expand Up @@ -328,6 +338,22 @@ public void putExtra(String name, Object value) {
catch (JSONException e) {}
}

/**
* Whether this container should route fullscreen direct-scanout drawables to a
* sibling Android `SurfaceControl` layer (HWC overlay plane / DPU scanout)
* instead of having `GLRenderer` composite them on the EGL surface. Default off.
*
* The setting is sampled once at activity startup and held for the session. Toggling
* it has no effect on a running game; the user must relaunch the container.
*/
public boolean isDirectCompositionEnabled() {
return "1".equals(getExtra(EXTRA_DIRECT_COMPOSITION, "0"));
}

public void setDirectCompositionEnabled(boolean enabled) {
putExtra(EXTRA_DIRECT_COMPOSITION, enabled ? "1" : "0");
}

public String getWineVersion() {
return wineVersion;
}
Expand Down
Loading