mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 19:41:10 +00:00
feat: Mali Vulkan support through MaliBridge (#2027)
This commit is contained in:
83 files changed
+2331
-80
No files matched your search
@@ -62,6 +62,7 @@ Java_com_droiddeck_launcher_gpu_VulkanInfo_nativeQuery(JNIEnv *env, jclass clazz
|
||||
LINE("driver_version=%u.%u.%u\n", p.driverVersion >> 22, (p.driverVersion >> 12) & 0x3ff, p.driverVersion & 0xfff);
|
||||
LINE("vendor_id=0x%04x\ndevice_id=0x%08x\n", p.vendorID, p.deviceID);
|
||||
LINE("max_image_2d=%u\n", p.limits.maxImageDimension2D);
|
||||
LINE("max_push_constants=%u\n", p.limits.maxPushConstantsSize);
|
||||
|
||||
uint32_t extCount = 0;
|
||||
if (vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, NULL);
|
||||
@@ -91,6 +92,7 @@ Java_com_droiddeck_launcher_gpu_VulkanInfo_nativeQuery(JNIEnv *env, jclass clazz
|
||||
vkGetPhysicalDeviceFeatures(device, &f);
|
||||
LINE("geometry_shader=%s\ntessellation_shader=%s\ntexture_bc=%s\n",
|
||||
f.geometryShader ? "yes" : "no", f.tessellationShader ? "yes" : "no", f.textureCompressionBC ? "yes" : "no");
|
||||
LINE("shader_int64=%s\n", f.shaderInt64 ? "yes" : "no");
|
||||
}
|
||||
if (vkGetPhysicalDeviceMemoryProperties) {
|
||||
VkPhysicalDeviceMemoryProperties m;
|
||||
|
||||
@@ -381,6 +381,26 @@ static int sniff_modifier(const struct droiddeck_native_handle *h, uint64_t *mod
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define SC_MOD_GRALLOC (~0ULL - 1)
|
||||
|
||||
static const char *pool_kind(uint64_t mod) {
|
||||
return mod == MOD_QCOM_COMPRESSED ? "UBWC" : mod == SC_MOD_GRALLOC ? "gralloc-laid-out" : "linear";
|
||||
}
|
||||
|
||||
static void describe_handle(const struct droiddeck_native_handle *h, char *out, size_t n) {
|
||||
size_t o = 0;
|
||||
out[0] = 0;
|
||||
if (!h) { snprintf(out, n, "no handle"); return; }
|
||||
for (int i = 0; i < h->numFds && i < 4 && o < n; i++) {
|
||||
off_t sz = lseek(h->data[i], 0, SEEK_END);
|
||||
if (sz >= 0) lseek(h->data[i], 0, SEEK_SET);
|
||||
o += (size_t)snprintf(out + o, n - o, "%sfd%d %lld bytes", i ? ", " : "", i, (long long)sz);
|
||||
}
|
||||
if (o < n) o += (size_t)snprintf(out + o, n - o, "; ints");
|
||||
for (int i = 0; i < h->numInts && i < 8 && o < n; i++)
|
||||
o += (size_t)snprintf(out + o, n - o, " %#x", (unsigned)h->data[h->numFds + i]);
|
||||
}
|
||||
|
||||
static int g_alloc_failed;
|
||||
|
||||
enum { TIER_UBWC, TIER_PLAIN, TIER_LINEAR };
|
||||
@@ -416,22 +436,54 @@ static int alloc_slot(struct layer *l, struct slot *s, int w, int h, uint32_t fm
|
||||
AHardwareBuffer_Desc got; AHardwareBuffer_describe(ahb, &got);
|
||||
const struct droiddeck_native_handle *nh = api.getNativeHandle(ahb);
|
||||
uint64_t mod = MOD_LINEAR;
|
||||
if (!sniff_modifier(nh, &mod)) {
|
||||
if (tier < TIER_LINEAR) {
|
||||
struct vkp_image *img = NULL;
|
||||
const char *path;
|
||||
char info[160] = "";
|
||||
if (sniff_modifier(nh, &mod)) {
|
||||
img = vkp_image_import_dmabuf(nh->data[0], fmt == AHB_RGB10A2 ? DRM_ABGR2101010 : DRM_ABGR8888, mod, w, h,
|
||||
got.stride * 4, 0, 1);
|
||||
path = "dma-buf (QTI handle)";
|
||||
} else {
|
||||
if (tier == TIER_UBWC) {
|
||||
char hd[256];
|
||||
describe_handle(nh, hd, sizeof(hd));
|
||||
droiddeck_log("layer", "%s: gralloc handle is not a QTI one (%d fds, %d ints: %s): no UBWC request; "
|
||||
"pool buffers are imported %s", l->name, nh ? nh->numFds : -1, nh ? nh->numInts : -1, hd,
|
||||
vkp_can_import_ahb() ? "as AHardwareBuffers (the driver takes gralloc's own layout)"
|
||||
: "as linear dma-bufs (this driver has no AHardwareBuffer import)");
|
||||
AHardwareBuffer_release(ahb);
|
||||
l->alloc_tier = TIER_LINEAR;
|
||||
droiddeck_log("layer", "%s: gralloc handle layout unknown (%d fds, %d ints): using linear pool buffers",
|
||||
l->name, nh ? nh->numFds : -1, nh ? nh->numInts : -1);
|
||||
l->alloc_tier = vkp_can_import_ahb() ? TIER_PLAIN : TIER_LINEAR;
|
||||
continue;
|
||||
}
|
||||
mod = MOD_LINEAR;
|
||||
mod = SC_MOD_GRALLOC;
|
||||
path = "AHardwareBuffer";
|
||||
img = vkp_image_import_ahb(ahb, info, sizeof(info));
|
||||
if (!img && tier == TIER_LINEAR) {
|
||||
const off_t need = (off_t)got.stride * 4 * h;
|
||||
int fd = -1, which = -1;
|
||||
for (int i = 0; nh && i < nh->numFds; i++) {
|
||||
off_t sz = lseek(nh->data[i], 0, SEEK_END);
|
||||
if (sz >= 0) lseek(nh->data[i], 0, SEEK_SET);
|
||||
if (sz >= need) { fd = nh->data[i]; which = i; break; }
|
||||
}
|
||||
if (fd < 0 && nh && nh->numFds > 0) { fd = nh->data[0]; which = 0; }
|
||||
mod = MOD_LINEAR;
|
||||
path = "linear dma-buf";
|
||||
img = fd >= 0 ? vkp_image_import_dmabuf(fd, fmt == AHB_RGB10A2 ? DRM_ABGR2101010 : DRM_ABGR8888, mod,
|
||||
w, h, got.stride * 4, 0, 1) : NULL;
|
||||
snprintf(info, sizeof(info), "handle fd %d of %d", which, nh ? nh->numFds : 0);
|
||||
if (vkp_can_import_ahb())
|
||||
droiddeck_log("layer", "%s: AHardwareBuffer import failed: the linear dma-buf in the handle's fd %d "
|
||||
"(of %d) -> %s", l->name, which, nh ? nh->numFds : 0, img ? "imported" : "failed too");
|
||||
}
|
||||
}
|
||||
int fd = (nh && nh->numFds > 0) ? nh->data[0] : -1;
|
||||
struct vkp_image *img = fd >= 0 ? vkp_image_import_dmabuf(fd, fmt == AHB_RGB10A2 ? DRM_ABGR2101010 : DRM_ABGR8888,
|
||||
mod, w, h, got.stride * 4, 0, 1) : NULL;
|
||||
if (!img) {
|
||||
droiddeck_log("layer", "%s: import of a %s %dx%d pool buffer (stride %u px) into the compositor's Turnip failed",
|
||||
l->name, mod == MOD_QCOM_COMPRESSED ? "UBWC" : "linear", w, h, got.stride);
|
||||
char hd[256];
|
||||
describe_handle(nh, hd, sizeof(hd));
|
||||
droiddeck_log("layer", "%s: import of a %s %dx%d pool buffer (stride %u px, %s, usage %#llx) into the "
|
||||
"compositor's Turnip failed via %s; gralloc handle %d fds / %d ints: %s", l->name,
|
||||
pool_kind(mod), w, h, got.stride, tier_name(tier), (unsigned long long)got.usage, path,
|
||||
nh ? nh->numFds : -1, nh ? nh->numInts : -1, hd);
|
||||
AHardwareBuffer_release(ahb);
|
||||
if (tier < TIER_LINEAR) { l->alloc_tier = tier + 1; continue; }
|
||||
g_alloc_failed = 1;
|
||||
@@ -442,10 +494,11 @@ static int alloc_slot(struct layer *l, struct slot *s, int w, int h, uint32_t fm
|
||||
if (l->logged_w != w || l->logged_h != h || l->logged_fmt != fmt || l->logged_mod != mod ||
|
||||
l->logged_tier != tier) {
|
||||
l->logged_w = w; l->logged_h = h; l->logged_fmt = fmt; l->logged_mod = mod; l->logged_tier = tier;
|
||||
droiddeck_log("layer", "%s: pool buffers %dx%d%s: %s (%s), stride %u px, up to %d buffers (gralloc handle %d fds / %d ints)",
|
||||
l->name, w, h, fmt == AHB_RGB10A2 ? " 10-bit RGBA1010102" : "",
|
||||
mod == MOD_QCOM_COMPRESSED ? "UBWC (QCOM_COMPRESSED)" : "linear", tier_name(tier),
|
||||
got.stride, l->pool_n, nh ? nh->numFds : -1, nh ? nh->numInts : -1);
|
||||
droiddeck_log("layer", "%s: pool buffers %dx%d%s: %s (%s), stride %u px, up to %d buffers (gralloc handle %d fds / "
|
||||
"%d ints), imported as %s%s%s%s", l->name, w, h, fmt == AHB_RGB10A2 ? " 10-bit RGBA1010102" : "",
|
||||
mod == MOD_QCOM_COMPRESSED ? "UBWC (QCOM_COMPRESSED)" : pool_kind(mod), tier_name(tier),
|
||||
got.stride, l->pool_n, nh ? nh->numFds : -1, nh ? nh->numInts : -1, path,
|
||||
info[0] ? " (" : "", info, info[0] ? ")" : "");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -814,7 +867,7 @@ int sc_layer_present(struct vkp_image *src, int scene_w, int scene_h, const stru
|
||||
l->first_logged = 1;
|
||||
droiddeck_log("layer", "presenting %dx%d game frames on their own SurfaceControl layer (%s pool, %d buffers); "
|
||||
"HUD and pointer stay Android views above it", sw, sh,
|
||||
l->pool_modifier == MOD_QCOM_COMPRESSED ? "UBWC" : "linear", l->pool_n);
|
||||
pool_kind(l->pool_modifier), l->pool_n);
|
||||
}
|
||||
vkp_signal_first_frame();
|
||||
return 0;
|
||||
@@ -836,7 +889,7 @@ int sc_layer_present_pass(const struct vkp_draw *draws, int n, int scene_w, int
|
||||
l->first_logged = 1;
|
||||
droiddeck_log("layer", "presenting %dx%d frames on their own SurfaceControl layer (%s pool, %d buffers); "
|
||||
"HUD and pointer stay Android views above it", rw, rh,
|
||||
l->pool_modifier == MOD_QCOM_COMPRESSED ? "UBWC" : "linear", l->pool_n);
|
||||
pool_kind(l->pool_modifier), l->pool_n);
|
||||
}
|
||||
vkp_signal_first_frame();
|
||||
return 0;
|
||||
@@ -874,12 +927,12 @@ int sc_layer_present_hdr_scene(const struct vkp_draw *draws, int n, const struct
|
||||
if (tm)
|
||||
droiddeck_log("color", "tone-mapped picture on the game's display layer: %dx%d, %s 8-bit buffers, untagged "
|
||||
"(sRGB) - HDR output is switched off", rw, rh,
|
||||
l->pool_modifier == MOD_QCOM_COMPRESSED ? "UBWC" : "linear");
|
||||
pool_kind(l->pool_modifier));
|
||||
else {
|
||||
char cov[96];
|
||||
coverage_text(l, cov, sizeof(cov));
|
||||
droiddeck_log("color", "HDR picture on its own display layer: %dx%d, %s %s buffers, tagged %s%s%s", rw, rh,
|
||||
l->pool_modifier == MOD_QCOM_COMPRESSED ? "UBWC" : "linear",
|
||||
pool_kind(l->pool_modifier),
|
||||
g_hdr_pool_fmt == AHB_RGB10A2 ? "10-bit" : "8-bit",
|
||||
color && color->dataspace == DROIDDECK_ADATASPACE_BT2020_PQ ? "BT2020_PQ" : "HDR",
|
||||
cov[0] ? ", " : "", cov);
|
||||
|
||||
@@ -93,6 +93,9 @@ static int g_black_state;
|
||||
static PFN_vkImportSemaphoreFdKHR g_import_sem_fd;
|
||||
static VkSemaphore g_wait_sem;
|
||||
|
||||
#define VKP_VENDOR_QUALCOMM 0x5143
|
||||
static uint32_t g_vendor_id;
|
||||
|
||||
static struct vkp_perf g_perf;
|
||||
static int64_t perf_now(void) {
|
||||
struct timespec ts;
|
||||
@@ -460,6 +463,7 @@ static int dev_init(void) {
|
||||
{
|
||||
VkPhysicalDeviceProperties props;
|
||||
g_vk.GetPhysicalDeviceProperties(g_pd, &props);
|
||||
g_vendor_id = props.vendorID;
|
||||
snprintf(g_gpu_name, sizeof(g_gpu_name), "%s", props.deviceName);
|
||||
droiddeck_log("gpu", "compositor renders on %s with %s", props.deviceName,
|
||||
g_library_name ? g_library_name : "the system Vulkan driver");
|
||||
@@ -989,8 +993,9 @@ struct vkp_image *vkp_image_import_dmabuf(int fd, uint32_t drm_format, uint64_t
|
||||
.allocationSize = req.size, .memoryTypeIndex = (uint32_t)idx};
|
||||
VkResult mr = g_vk.AllocateMemory(g_dev, &mai, NULL, &img->mem);
|
||||
if (mr != VK_SUCCESS) {
|
||||
LOGE("%s: importing the %s dma-buf (%dx%d, %llu bytes) -> %s", as_blit_dst ? "layer" : "dmabuf",
|
||||
vkp_modifier_name(modifier), w, h, (unsigned long long)req.size, vk_result_name(mr));
|
||||
LOGE("%s: importing the %s dma-buf (%dx%d, pitch %u, offset %u, %llu bytes, memory type %d of image types "
|
||||
"%#x & fd types %#x) -> %s", as_blit_dst ? "layer" : "dmabuf", vkp_modifier_name(modifier), w, h,
|
||||
stride, offset, (unsigned long long)req.size, idx, req.memoryTypeBits, allowed, vk_result_name(mr));
|
||||
g_vk.DestroyImage(g_dev, img->image, NULL); close(dupfd); free(img); return NULL;
|
||||
}
|
||||
VkResult br = g_vk.BindImageMemory(g_dev, img->image, img->mem, 0);
|
||||
@@ -1057,6 +1062,101 @@ struct vkp_image *vkp_image_from_ahb(AHardwareBuffer *ahb) {
|
||||
return img;
|
||||
}
|
||||
|
||||
int vkp_can_import_ahb(void) {
|
||||
return dev_init() == 0 && g_vendor_id != VKP_VENDOR_QUALCOMM && g_ahb_import &&
|
||||
g_vk.GetAndroidHardwareBufferPropertiesANDROID;
|
||||
}
|
||||
|
||||
struct vkp_image *vkp_image_import_ahb(struct AHardwareBuffer *ahb, char *info, size_t info_n) {
|
||||
if (info && info_n) info[0] = 0;
|
||||
if (!ahb || !vkp_can_import_ahb()) return NULL;
|
||||
AHardwareBuffer_Desc d;
|
||||
AHardwareBuffer_describe(ahb, &d);
|
||||
VkAndroidHardwareBufferFormatPropertiesANDROID fp = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID};
|
||||
VkAndroidHardwareBufferPropertiesANDROID hp = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID, .pNext = &fp};
|
||||
VkResult pr = g_vk.GetAndroidHardwareBufferPropertiesANDROID(g_dev, ahb, &hp);
|
||||
if (pr != VK_SUCCESS) {
|
||||
LOGE("layer: vkGetAndroidHardwareBufferPropertiesANDROID(%ux%u, AHB format %#x, usage %#llx) -> %s", d.width,
|
||||
d.height, d.format, (unsigned long long)d.usage, vk_result_name(pr));
|
||||
return NULL;
|
||||
}
|
||||
if (fp.format == VK_FORMAT_UNDEFINED) {
|
||||
LOGE("layer: the driver reports the %ux%u AHB (format %#x) as external format %#llx only: no blit target",
|
||||
d.width, d.height, d.format, (unsigned long long)fp.externalFormat);
|
||||
return NULL;
|
||||
}
|
||||
const VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
if (g_vk.GetPhysicalDeviceImageFormatProperties2) {
|
||||
VkPhysicalDeviceExternalImageFormatInfo ext = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
|
||||
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID};
|
||||
VkPhysicalDeviceImageFormatInfo2 qi = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, .pNext = &ext, .format = fp.format,
|
||||
.type = VK_IMAGE_TYPE_2D, .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = usage};
|
||||
VkExternalImageFormatProperties extp = {.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES};
|
||||
VkImageFormatProperties2 ip = {.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, .pNext = &extp};
|
||||
VkResult qr = g_vk.GetPhysicalDeviceImageFormatProperties2(g_pd, &qi, &ip);
|
||||
if (qr != VK_SUCCESS ||
|
||||
!(extp.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT)) {
|
||||
LOGE("layer: VkFormat %d as an imported AHB blit target is not supported (%s, external features %#x)",
|
||||
(int)fp.format, vk_result_name(qr), extp.externalMemoryProperties.externalMemoryFeatures);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
struct vkp_image *img = calloc(1, sizeof(*img));
|
||||
if (!img) return NULL;
|
||||
img->w = (int)d.width; img->h = (int)d.height;
|
||||
img->dmabuf = 1; img->blit_dst = 1;
|
||||
img->fmt = fp.format;
|
||||
VkExternalMemoryImageCreateInfo extImg = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID};
|
||||
VkImageCreateInfo ici = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = &extImg,
|
||||
.imageType = VK_IMAGE_TYPE_2D, .format = fp.format, .extent = {d.width, d.height, 1},
|
||||
.mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
VkResult cr = g_vk.CreateImage(g_dev, &ici, NULL, &img->image);
|
||||
if (cr != VK_SUCCESS) {
|
||||
LOGE("layer: vkCreateImage(AHB %ux%u, VkFormat %d) -> %s", d.width, d.height, (int)fp.format,
|
||||
vk_result_name(cr));
|
||||
free(img); return NULL;
|
||||
}
|
||||
int idx = -1;
|
||||
for (int i = 0; i < 32; i++) if (hp.memoryTypeBits & (1u << i)) { idx = i; break; }
|
||||
if (idx < 0) {
|
||||
LOGE("layer: no memory type can import the %ux%u AHB (memoryTypeBits 0)", d.width, d.height);
|
||||
g_vk.DestroyImage(g_dev, img->image, NULL); free(img); return NULL;
|
||||
}
|
||||
VkImportAndroidHardwareBufferInfoANDROID imp = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID, .buffer = ahb};
|
||||
VkMemoryDedicatedAllocateInfo ded = {.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
||||
.pNext = &imp, .image = img->image};
|
||||
VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = &ded,
|
||||
.allocationSize = hp.allocationSize, .memoryTypeIndex = (uint32_t)idx};
|
||||
VkResult mr = g_vk.AllocateMemory(g_dev, &mai, NULL, &img->mem);
|
||||
if (mr != VK_SUCCESS) {
|
||||
LOGE("layer: importing the %ux%u AHB (VkFormat %d, %llu bytes, memory type %d of memoryTypeBits %#x) -> %s",
|
||||
d.width, d.height, (int)fp.format, (unsigned long long)hp.allocationSize, idx, hp.memoryTypeBits,
|
||||
vk_result_name(mr));
|
||||
g_vk.DestroyImage(g_dev, img->image, NULL); free(img); return NULL;
|
||||
}
|
||||
VkResult br = g_vk.BindImageMemory(g_dev, img->image, img->mem, 0);
|
||||
if (br != VK_SUCCESS) {
|
||||
LOGE("layer: vkBindImageMemory(AHB) -> %s", vk_result_name(br));
|
||||
g_vk.FreeMemory(g_dev, img->mem, NULL); g_vk.DestroyImage(g_dev, img->image, NULL);
|
||||
free(img); return NULL;
|
||||
}
|
||||
if (info && info_n)
|
||||
snprintf(info, info_n, "VkFormat %d, %llu bytes, memory type %d of memoryTypeBits %#x", (int)fp.format,
|
||||
(unsigned long long)hp.allocationSize, idx, hp.memoryTypeBits);
|
||||
return img;
|
||||
}
|
||||
|
||||
static int shm_sampled(void) {
|
||||
static int known;
|
||||
if (!known) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef VK_PRESENT_H
|
||||
#define VK_PRESENT_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <android/native_window.h>
|
||||
void vk_present_set_driver(const char *driver_path, const char *library_name,
|
||||
const char *native_lib_dir);
|
||||
@@ -33,6 +34,9 @@ struct vkp_image *vkp_image_import_dmabuf(int fd, uint32_t drm_format, uint64_t
|
||||
int w, int h, uint32_t stride, uint32_t offset,
|
||||
int as_blit_dst);
|
||||
int vkp_image_is_dmabuf(const struct vkp_image *img);
|
||||
struct AHardwareBuffer;
|
||||
int vkp_can_import_ahb(void);
|
||||
struct vkp_image *vkp_image_import_ahb(struct AHardwareBuffer *ahb, char *info, size_t info_n);
|
||||
|
||||
struct vkp_image *vkp_image_create_shm(int w, int h);
|
||||
struct AHardwareBuffer;
|
||||
|
||||
@@ -65,7 +65,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
}
|
||||
|
||||
fun state() = GpuDriversState(
|
||||
gpuName = gpu.displayName(activity), gpuFamily = gpu.family.label(activity), soc = gpu.soc, supportText = gpu.supportText(activity),
|
||||
gpuName = gpu.displayName(activity), gpuFamily = gpu.family.label(activity), soc = gpu.soc, supportText = gpu.supportText(activity, com.droiddeck.launcher.core.DeviceSupport.vkBridge(activity)),
|
||||
supported = gpu.support == GpuInfo.Support.TESTED, unsupported = gpu.support == GpuInfo.Support.UNSUPPORTED,
|
||||
auto = mode == SessionPrefs.GPU_DRIVERS_AUTO, pairs = pairRows, busy = pairBusy, percent = pairPercent,
|
||||
autoStatus = autoStatus, releaseStatus = releaseStatus.ifEmpty { activity.getString(R.string.drivers_not_checked) }, checking = releaseChecking,
|
||||
@@ -264,7 +264,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
return
|
||||
}
|
||||
if (DriverPairs.recommendedKey(gpu, emptyList()) == null) {
|
||||
autoStatus = activity.getString(R.string.drivers_none_to_set, gpu.supportText(activity).lowercase())
|
||||
autoStatus = activity.getString(R.string.drivers_none_to_set, gpu.supportText(activity, com.droiddeck.launcher.core.DeviceSupport.vkBridge(activity)).lowercase())
|
||||
return
|
||||
}
|
||||
releaseChecking = true
|
||||
|
||||
@@ -163,6 +163,10 @@ class FrontEndActivity : ComponentActivity() {
|
||||
private var gameCores by mutableStateOf<Set<Int>>(emptySet())
|
||||
private var tuSysmem by mutableStateOf(false)
|
||||
private var zinkLazy by mutableStateOf(false)
|
||||
private var vkBridge by mutableStateOf(false)
|
||||
private var vkBridgeSafe by mutableStateOf(true)
|
||||
private var bridgeProtonDxvk by mutableStateOf(false)
|
||||
private val adrenoGpu by lazy { com.droiddeck.launcher.core.DeviceSupport.adreno() }
|
||||
private var noXalia by mutableStateOf(true)
|
||||
private var fastSync by mutableStateOf(false)
|
||||
private var syncFallback by mutableStateOf(true)
|
||||
@@ -1076,6 +1080,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
FrontEndScreen(
|
||||
FrontEndState(
|
||||
installed = installed, ready = ready, available = available?.version, removalPending = removalPending, runtimeActionsBlocked = runtimeChangesBlocked(),
|
||||
vkBridge = vkBridge, vkBridgeSafe = vkBridgeSafe, bridgeProtonDxvk = bridgeProtonDxvk, adreno = adrenoGpu,
|
||||
shortcutPicker = shortcutPicker,
|
||||
shortcutLibraryScanning = shortcutLibraryScanning,
|
||||
gameSyncFolder = gameSyncFolder,
|
||||
@@ -1224,6 +1229,15 @@ class FrontEndActivity : ComponentActivity() {
|
||||
onInstallPackage = { id -> installPackage(id) },
|
||||
onRemovePackage = { id -> removePackage(id) },
|
||||
onRuntime = { onRuntimeButton() },
|
||||
onVkBridge = { on ->
|
||||
SessionPrefs.setVkBridge(this, on); vkBridge = on
|
||||
if (!on) com.droiddeck.launcher.session.ComponentsManager.settleAutoDxvk(this)
|
||||
},
|
||||
onVkBridgeSafe = { on -> SessionPrefs.setVkBridgeSafe(this, on); vkBridgeSafe = on },
|
||||
onBridgeProtonDxvk = { on ->
|
||||
SessionPrefs.setBridgeProtonDxvk(this, on); bridgeProtonDxvk = on
|
||||
if (on) com.droiddeck.launcher.session.ComponentsManager.settleAutoDxvk(this)
|
||||
},
|
||||
onFrameGenPick = { mode ->
|
||||
FrameGen.set(this, mode)
|
||||
frameGenLabel = FrameGen.label(this)
|
||||
@@ -2534,6 +2548,9 @@ class FrontEndActivity : ComponentActivity() {
|
||||
gameCores = CpuCores.parse(SessionPrefs.gameCpus(this)).ifEmpty { CpuCores.all.toSet() }
|
||||
tuSysmem = SessionPrefs.tuSysmem(this)
|
||||
zinkLazy = SessionPrefs.zinkLazy(this)
|
||||
vkBridge = SessionPrefs.vkBridge(this)
|
||||
vkBridgeSafe = SessionPrefs.vkBridgeSafe(this)
|
||||
bridgeProtonDxvk = SessionPrefs.bridgeProtonDxvk(this)
|
||||
noXalia = SessionPrefs.noXalia(this)
|
||||
fastSync = SessionPrefs.fastSync(this)
|
||||
syncFallback = SessionPrefs.syncFallback(this)
|
||||
@@ -2570,6 +2587,9 @@ class FrontEndActivity : ComponentActivity() {
|
||||
offline = OfflineMode.enabled(this)
|
||||
installed = LinuxRuntimeInstaller.installedVersion(this)
|
||||
ready = LinuxRuntime.isInstalled(this)
|
||||
vkBridge = SessionPrefs.vkBridge(this)
|
||||
vkBridgeSafe = SessionPrefs.vkBridgeSafe(this)
|
||||
bridgeProtonDxvk = SessionPrefs.bridgeProtonDxvk(this)
|
||||
removalPending = LinuxRuntimeInstaller.hasRemovalPending(this)
|
||||
ui.post { autoUpdateRuntime() }
|
||||
frameGenLabel = FrameGen.label(this)
|
||||
@@ -2674,7 +2694,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
Thread({ checkCatalog() }, "catalog").start()
|
||||
return
|
||||
}
|
||||
if (installed == null && !com.droiddeck.launcher.core.DeviceSupport.adreno()) { showNonAdreno = release; return }
|
||||
if (installed == null && !com.droiddeck.launcher.core.DeviceSupport.canDraw(this)) { showNonAdreno = release; return }
|
||||
install(release)
|
||||
}
|
||||
|
||||
@@ -2693,7 +2713,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
showPhantomGate = true
|
||||
return false
|
||||
}
|
||||
val warn = installed == null && !com.droiddeck.launcher.core.DeviceSupport.adreno()
|
||||
val warn = installed == null && !com.droiddeck.launcher.core.DeviceSupport.canDraw(this)
|
||||
if (warn && available != null) { showNonAdreno = available; return false }
|
||||
startActivity(intent)
|
||||
return true
|
||||
|
||||
@@ -208,7 +208,7 @@ object DeviceReport {
|
||||
|
||||
h("Drivers")
|
||||
val gpu = com.droiddeck.launcher.gpu.GpuInfo.detect()
|
||||
k("GPU", "${gpu.name} · ${gpu.family.label} · ${gpu.supportText}" + if (gpu.oneUi8Gen2) " · One UI 8 Gen 2" else "")
|
||||
k("GPU", "${gpu.name} · ${gpu.family.label} · ${gpu.supportText(SessionPrefs.vkBridge(context))}" + if (gpu.oneUi8Gen2) " · One UI 8 Gen 2" else "")
|
||||
if (gpu.model == 740) {
|
||||
val hint = com.droiddeck.launcher.gpu.TpUbwcHint
|
||||
k("TP UBWC hint", hint.forThisDevice(context) ?: "driver default")
|
||||
|
||||
@@ -6,8 +6,20 @@ import com.droiddeck.launcher.R
|
||||
import java.io.File
|
||||
|
||||
object DeviceSupport {
|
||||
fun adreno(): Boolean =
|
||||
private val adrenoGpu by lazy {
|
||||
File("/sys/class/kgsl/kgsl-3d0").exists() || File("/vendor/lib64/hw/vulkan.adreno.so").exists()
|
||||
}
|
||||
|
||||
private val maliGpu by lazy { !adrenoGpu && File("/dev/mali0").exists() }
|
||||
|
||||
fun adreno(): Boolean = adrenoGpu
|
||||
|
||||
fun mali(): Boolean = maliGpu
|
||||
|
||||
fun vkBridge(context: Context): Boolean =
|
||||
!adreno() && com.droiddeck.launcher.session.SessionPrefs.vkBridge(context)
|
||||
|
||||
fun canDraw(context: Context): Boolean = adreno() || vkBridge(context)
|
||||
|
||||
fun gpuName(context: Context): String {
|
||||
val soc = if (Build.VERSION.SDK_INT >= 31) Build.SOC_MODEL.takeIf { it.isNotBlank() && it != Build.UNKNOWN } else null
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.droiddeck.launcher.core.DeviceSupport
|
||||
import com.droiddeck.launcher.core.FileUtils
|
||||
import java.io.File
|
||||
|
||||
data class AppVulkan(
|
||||
val major: Int,
|
||||
val minor: Int,
|
||||
val shaderInt64: Boolean?,
|
||||
val maxPushConstants: Int?,
|
||||
val source: String,
|
||||
) {
|
||||
fun atLeast(major: Int, minor: Int): Boolean = this.major > major || (this.major == major && this.minor >= minor)
|
||||
|
||||
val label: String
|
||||
get() = "Vulkan $major.$minor, shaderInt64 ${shaderInt64?.let { if (it) "yes" else "no" } ?: "?"}, " +
|
||||
"push constants ${maxPushConstants ?: "?"} ($source)"
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AppVulkan"
|
||||
const val BRIDGE_FILE = "apps-vulkan"
|
||||
|
||||
fun detect(context: Context): AppVulkan? {
|
||||
if (!DeviceSupport.vkBridge(context)) return null
|
||||
val native = VulkanInfo.query()
|
||||
val file = File(VkBridgeComponent.cacheDir(context), BRIDGE_FILE)
|
||||
val fromBridge = FileUtils.readString(file)?.let { fromBridgeFile(it, native["device"], native["api"]) }
|
||||
val found = fromBridge ?: fromDriver(native)
|
||||
Log.i(TAG, found?.label ?: "unknown (driver query: ${native["error"] ?: "no api line"})")
|
||||
return found
|
||||
}
|
||||
|
||||
internal fun fromBridgeFile(text: String, driverDevice: String?, driverApi: String? = null): AppVulkan? {
|
||||
val map = keyValues(text)
|
||||
val device = map["device"]
|
||||
if (device != null && driverDevice != null && device != driverDevice) return null
|
||||
val fileDriver = version(map["driver_api"])
|
||||
val native = version(driverApi)
|
||||
if (fileDriver != null && native != null && fileDriver != native) return null
|
||||
val promoted = map["promotion"]?.trim()?.lowercase() == "on"
|
||||
val (major, minor) = (if (promoted) version(map["api"]) else fileDriver ?: version(map["api"])) ?: return null
|
||||
return AppVulkan(major, minor, bool(map["shader_int64"]), map["max_push_constants"]?.toIntOrNull(),
|
||||
if (promoted) "bridge, promoted" else "bridge")
|
||||
}
|
||||
|
||||
internal fun fromDriver(map: Map<String, String>): AppVulkan? {
|
||||
val (major, minor) = version(map["api"]) ?: return null
|
||||
return AppVulkan(major, minor, bool(map["shader_int64"]), map["max_push_constants"]?.toIntOrNull(), "driver")
|
||||
}
|
||||
|
||||
private fun keyValues(text: String): Map<String, String> = text.lineSequence().mapNotNull { line ->
|
||||
val eq = line.indexOf('=')
|
||||
if (eq <= 0) null else line.substring(0, eq).trim() to line.substring(eq + 1).trim()
|
||||
}.toMap()
|
||||
|
||||
private fun version(v: String?): Pair<Int, Int>? {
|
||||
val parts = v?.trim()?.split('.') ?: return null
|
||||
if (parts.size < 2) return null
|
||||
val major = parts[0].toIntOrNull() ?: return null
|
||||
val minor = parts[1].toIntOrNull() ?: return null
|
||||
return major to minor
|
||||
}
|
||||
|
||||
private fun bool(v: String?): Boolean? = when (v?.trim()?.lowercase()) {
|
||||
"1", "yes", "true" -> true
|
||||
"0", "no", "false" -> false
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import com.droiddeck.launcher.core.SessionPart
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class ClockLogComponent(private val logFile: File) : SessionPart() {
|
||||
@Volatile private var running = false
|
||||
private var thread: Thread? = null
|
||||
|
||||
override fun start() {
|
||||
running = true
|
||||
thread = Thread({ run() }, "clock-log").apply { isDaemon = true; start() }
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
running = false
|
||||
thread?.interrupt()
|
||||
thread = null
|
||||
}
|
||||
|
||||
private fun run() {
|
||||
val policies = File("/sys/devices/system/cpu/cpufreq").listFiles { f -> f.name.startsWith("policy") }
|
||||
?.sortedBy { it.name.removePrefix("policy").toIntOrNull() ?: 0 } ?: emptyList()
|
||||
val gpuCandidates = gpuSources()
|
||||
val gpu = gpuCandidates.firstOrNull { !it.startsWith("/proc/") && firstNumber(it) != null }
|
||||
val power = app().getSystemService(Context.POWER_SERVICE) as? PowerManager
|
||||
val time = SimpleDateFormat("HH:mm:ss", Locale.US)
|
||||
try {
|
||||
FileWriter(logFile, true).use { w ->
|
||||
w.write("== clocks: ${Build.MANUFACTURER} ${Build.MODEL} (${Build.HARDWARE}), every ${PERIOD_MS / 1000}s\n")
|
||||
w.write("cpu policies: ${policies.joinToString { p -> p.name + " " + (File(p, "related_cpus").readTextOrNull()?.trim() ?: "?") }}\n")
|
||||
for (source in gpuCandidates) {
|
||||
val f = File(source)
|
||||
val state = when {
|
||||
!f.exists() -> continue
|
||||
firstNumber(source) != null -> "readable"
|
||||
else -> "refused"
|
||||
}
|
||||
w.write("gpu source $source: $state\n")
|
||||
}
|
||||
w.write("gpu clock from: ${gpu ?: "nothing readable"}\n")
|
||||
w.flush()
|
||||
while (running && logFile.length() < MAX_BYTES) {
|
||||
val line = StringBuilder(time.format(Date()))
|
||||
for (p in policies) {
|
||||
val cur = firstNumber(File(p, "scaling_cur_freq").path)
|
||||
val max = firstNumber(File(p, "scaling_max_freq").path)
|
||||
val hw = firstNumber(File(p, "cpuinfo_max_freq").path)
|
||||
line.append(" ${p.name.removePrefix("policy").let { "p$it" }} ${mhz(cur)}/${mhz(max)}")
|
||||
if (hw != null && max != null && max < hw) line.append("(of ${mhz(hw)})")
|
||||
}
|
||||
gpu?.let { line.append(" | gpu ${mhz(firstNumber(it))} MHz") }
|
||||
if (power != null && Build.VERSION.SDK_INT >= 29) {
|
||||
line.append(" | thermal ${power.currentThermalStatus}")
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
val h = power.getThermalHeadroom(10)
|
||||
if (!h.isNaN()) line.append(String.format(Locale.US, " headroom %.2f", h))
|
||||
}
|
||||
}
|
||||
w.write(line.append('\n').toString())
|
||||
w.flush()
|
||||
try { Thread.sleep(PERIOD_MS) } catch (e: InterruptedException) { break }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "clock log stopped: $e")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SessionService"
|
||||
private const val PERIOD_MS = 2000L
|
||||
private const val MAX_BYTES = 4L shl 20
|
||||
|
||||
private fun gpuSources(): List<String> {
|
||||
val list = mutableListOf(
|
||||
"/sys/kernel/ged/hal/current_freqency", "/sys/kernel/ged/hal/current_frequency",
|
||||
"/proc/gpufreqv2/gpufreq_status", "/proc/gpufreq/gpufreq_var_dump",
|
||||
"/sys/kernel/gpu/gpu_clock", "/sys/class/misc/mali0/device/cur_freq",
|
||||
"/sys/class/misc/mali0/device/clock",
|
||||
)
|
||||
File("/sys/class/devfreq").list()?.sorted()?.forEach { node ->
|
||||
if (node.contains("mali", true) || node.contains("gpu", true)) list.add("/sys/class/devfreq/$node/cur_freq")
|
||||
}
|
||||
return list.distinct()
|
||||
}
|
||||
|
||||
private fun File.readTextOrNull(): String? = runCatching { readText() }.getOrNull()
|
||||
|
||||
private fun firstNumber(path: String): Long? =
|
||||
runCatching { Regex("\\d+").find(File(path).readText())?.value?.toLong() }.getOrNull()
|
||||
|
||||
private fun mhz(raw: Long?): String = when {
|
||||
raw == null -> "?"
|
||||
raw > 10_000_000 -> (raw / 1_000_000).toString()
|
||||
raw > 10_000 -> (raw / 1_000).toString()
|
||||
else -> raw.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ object DeviceInfo {
|
||||
val vk = VulkanInfo.query()
|
||||
val gpu = GpuInfo.detect()
|
||||
return listOf(
|
||||
chip(), gpu(gpu), cpu(), memory(context), system(), vulkan(context, vk), drivers(context, gpu),
|
||||
chip(), gpu(context, gpu), cpu(), memory(context), system(), vulkan(context, vk), drivers(context, gpu),
|
||||
).filter { it.rows.isNotEmpty() }
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ object DeviceInfo {
|
||||
add("Family", read("/sys/devices/soc0/family"))
|
||||
})
|
||||
|
||||
private fun gpu(gpu: GpuInfo) = Section("GPU", buildList<Pair<String, String>> {
|
||||
private fun gpu(context: Context, gpu: GpuInfo) = Section("GPU", buildList<Pair<String, String>> {
|
||||
add("GPU", gpu.name)
|
||||
add("Family", gpu.family.label)
|
||||
add("Model found from", when (gpu.modelSource) {
|
||||
@@ -66,7 +66,7 @@ object DeviceInfo {
|
||||
add("Clock range", gpuRange())
|
||||
add("Load", read("$kgsl/gpu_busy_percentage")?.let { if (it.endsWith("%")) it else "$it %" })
|
||||
add("Temperature", read("$kgsl/temp")?.toLongOrNull()?.let { if (it > 1000) "${it / 1000} °C" else "$it °C" })
|
||||
add("Support", gpu.supportText)
|
||||
add("Support", gpu.supportText(SessionPrefs.vkBridge(context)))
|
||||
})
|
||||
|
||||
private fun cpu() = Section("CPU", buildList<Pair<String, String>> {
|
||||
|
||||
@@ -53,6 +53,18 @@ data class GpuInfo(
|
||||
Support.UNSUPPORTED -> "Not supported: DroidDeck needs an Adreno (Snapdragon) GPU"
|
||||
}
|
||||
|
||||
fun supportText(bridge: Boolean): String = when {
|
||||
support != Support.UNSUPPORTED -> supportText
|
||||
bridge -> "Runs through the Mali bridge, on the phone's own Vulkan driver"
|
||||
else -> "Not supported with the Mali bridge off: Turnip needs an Adreno (Snapdragon) GPU"
|
||||
}
|
||||
|
||||
fun supportText(context: Context, bridge: Boolean): String = when {
|
||||
support != Support.UNSUPPORTED -> supportText(context)
|
||||
bridge -> context.getString(R.string.gpuinfo_bridge)
|
||||
else -> context.getString(R.string.gpuinfo_bridge_off)
|
||||
}
|
||||
|
||||
fun supportText(context: Context): String = context.getString(when (support) {
|
||||
Support.TESTED -> R.string.gpuinfo_supported
|
||||
Support.UNTESTED -> when {
|
||||
|
||||
@@ -291,6 +291,10 @@ public final class TurnipDriver {
|
||||
}
|
||||
String quest = QuestExperimental.INSTANCE.defaultAndroidDriver();
|
||||
if (quest != null) return quest;
|
||||
if (com.droiddeck.launcher.core.DeviceSupport.INSTANCE.vkBridge(context)) {
|
||||
Log.i(TAG, "Mali bridge: the compositor uses the system Vulkan driver");
|
||||
return null;
|
||||
}
|
||||
GpuInfo gpu = gpuDetector.get();
|
||||
if (gpu.getFamily() == GpuInfo.Family.NOT_ADRENO) {
|
||||
Log.i(TAG, "not an Adreno GPU; leaving the display driver to the system");
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import com.droiddeck.launcher.core.HostProcess
|
||||
import com.droiddeck.launcher.core.SessionPart
|
||||
import java.io.File
|
||||
|
||||
class VkBridgeComponent(private val socket: File, private val logFile: File) : SessionPart() {
|
||||
@Volatile private var pid = -1
|
||||
@Volatile private var exited = false
|
||||
|
||||
override fun start() {
|
||||
stop()
|
||||
val binary = File(app().applicationInfo.nativeLibraryDir, BINARY)
|
||||
if (!binary.isFile) {
|
||||
Log.e(TAG, "server missing at $binary; the session will have no Vulkan")
|
||||
return
|
||||
}
|
||||
socket.parentFile?.mkdirs()
|
||||
val cacheDir = cacheDir(app())
|
||||
writeMode(cacheDir, com.droiddeck.launcher.session.SessionPrefs.vkBridgeMode(app()))
|
||||
if (VkBridgeNative.start(app(), socket, logFile, cacheDir)) {
|
||||
Log.i(TAG, "server ready in the app's process at $socket")
|
||||
return
|
||||
}
|
||||
socket.delete()
|
||||
exited = false
|
||||
val cmd = listOf(binary.absolutePath, "--socket", socket.absolutePath, "--log", logFile.absolutePath,
|
||||
"--cache-dir", cacheDir.absolutePath)
|
||||
.joinToString(" ") { it.replace("\\", "\\\\").replace(" ", "\\ ") }
|
||||
val env = arrayOf("HOME=" + app().filesDir)
|
||||
pid = HostProcess.start(cmd, env, app().filesDir, { status ->
|
||||
exited = true
|
||||
Log.w(TAG, "server exited with status $status")
|
||||
runCatching { logFile.appendText("== vkbridge server exited with status $status\n") }
|
||||
}, { line -> Log.i(TAG, line) })
|
||||
val began = System.currentTimeMillis()
|
||||
val deadline = began + 15_000L
|
||||
while (!socket.exists() && !exited && System.currentTimeMillis() < deadline) {
|
||||
try { Thread.sleep(50L) } catch (e: InterruptedException) { Thread.currentThread().interrupt(); break }
|
||||
}
|
||||
val waited = System.currentTimeMillis() - began
|
||||
Log.i(TAG, if (socket.exists()) "server ready (pid $pid) at $socket after $waited ms"
|
||||
else "server NOT ready (pid $pid, exited=$exited) after $waited ms; see $logFile")
|
||||
if (!socket.exists() && VkBridgeNative.start(app(), socket, logFile, cacheDir)) Log.i(TAG, "server ready in the app's process at $socket")
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
if (pid > 0) {
|
||||
Process.killProcess(pid)
|
||||
pid = -1
|
||||
}
|
||||
if (!VkBridgeNative.serving) socket.delete()
|
||||
}
|
||||
|
||||
override fun suspendPid(): Int = pid
|
||||
|
||||
companion object {
|
||||
private const val TAG = "VkBridge"
|
||||
private const val CACHE_DIR = "vkbridge-pipelines"
|
||||
private const val MODE_FILE = "mode"
|
||||
|
||||
fun cacheDir(context: Context): File {
|
||||
val dir = File(context.filesDir, CACHE_DIR)
|
||||
val old = File(context.cacheDir, CACHE_DIR)
|
||||
if (old.isDirectory) migrate(old, dir)
|
||||
dir.mkdirs()
|
||||
return dir
|
||||
}
|
||||
|
||||
fun writeMode(dir: File, mode: String) {
|
||||
val file = File(dir, MODE_FILE)
|
||||
if (runCatching { file.readText().trim() }.getOrNull() == mode) return
|
||||
val tmp = File(dir, "$MODE_FILE.tmp")
|
||||
runCatching {
|
||||
tmp.writeText(mode + "\n")
|
||||
if (!tmp.renameTo(file)) error("rename failed")
|
||||
Log.i(TAG, "bridge mode: $mode")
|
||||
}.onFailure { Log.w(TAG, "could not write $file: $it"); tmp.delete() }
|
||||
}
|
||||
|
||||
private fun migrate(old: File, dir: File) {
|
||||
val moved = if (!dir.exists()) old.renameTo(dir) else false
|
||||
if (!moved) {
|
||||
dir.mkdirs()
|
||||
old.listFiles()?.forEach { f ->
|
||||
val to = File(dir, f.name)
|
||||
if (to.exists()) return@forEach
|
||||
if (!f.renameTo(to)) runCatching { f.copyRecursively(to, overwrite = false) }
|
||||
}
|
||||
old.deleteRecursively()
|
||||
}
|
||||
Log.i(TAG, "bridge cache moved from $old to $dir")
|
||||
}
|
||||
|
||||
const val BINARY = "libvkbridge_server.so"
|
||||
const val GUEST_ICD = "/usr/local/share/vkbridge/droidbridge_icd.json"
|
||||
const val GUEST_LIB = "/usr/local/lib/vkbridge/libvulkan_droidbridge.so"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
|
||||
object VkBridgeNative {
|
||||
private const val TAG = "VkBridge"
|
||||
|
||||
@Volatile private var loaded: Boolean? = null
|
||||
|
||||
private fun load(context: Context): Boolean {
|
||||
loaded?.let { return it }
|
||||
val ok = try {
|
||||
val dev = File(context.filesDir, "vkbridge/dev/libvkbridge_jni.so")
|
||||
val debuggable = context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
|
||||
if (debuggable && dev.isFile) {
|
||||
Log.w(TAG, "in-app server from the development override $dev")
|
||||
System.load(dev.absolutePath)
|
||||
} else {
|
||||
System.loadLibrary("vkbridge_jni")
|
||||
}
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
Log.w(TAG, "in-app server unavailable: $e")
|
||||
false
|
||||
}
|
||||
loaded = ok
|
||||
return ok
|
||||
}
|
||||
|
||||
@Volatile var serving = false
|
||||
private set
|
||||
|
||||
fun start(context: Context, socket: File, log: File, cacheDir: File): Boolean {
|
||||
if (!load(context)) return false
|
||||
val ok = try {
|
||||
nativeStart(socket.absolutePath, log.absolutePath, cacheDir.absolutePath) == 1
|
||||
} catch (e: Throwable) {
|
||||
Log.w(TAG, "in-app server failed to start: $e")
|
||||
false
|
||||
}
|
||||
if (ok) serving = true
|
||||
return ok
|
||||
}
|
||||
|
||||
@JvmStatic private external fun nativeStart(socket: String, log: String, cacheDir: String): Int
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import com.droiddeck.launcher.gpu.FrameGen
|
||||
import com.droiddeck.launcher.gpu.GpuInfo
|
||||
import com.droiddeck.launcher.runtime.LinuxRuntime
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import com.droiddeck.launcher.session.SessionService
|
||||
import com.droiddeck.launcher.session.SessionState
|
||||
import com.droiddeck.launcher.ui.Motion
|
||||
import com.droiddeck.launcher.wayland.WaylandCompositor
|
||||
import java.io.File
|
||||
@@ -56,7 +58,10 @@ class DroidDeckHudHost(context: Context) {
|
||||
if (!gpuName.isNullOrBlank()) handler.post { this@DroidDeckHudHost.gpuName = cleanGpuName(gpuName) }
|
||||
if (window != null && window != lastWindow) {
|
||||
lastWindow = window
|
||||
handler.post { onPresentingWindowChanged?.invoke() }
|
||||
handler.post {
|
||||
onPresentingWindowChanged?.invoke()
|
||||
applyShown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,8 +179,9 @@ class DroidDeckHudHost(context: Context) {
|
||||
private fun applyShown() {
|
||||
if (!running) return
|
||||
val show = !hidden && !backgrounded
|
||||
view?.visibility = if (show) View.VISIBLE else View.GONE
|
||||
showing = show
|
||||
val visible = show && SessionState.mode != SessionService.MODE_DESKTOP
|
||||
view?.visibility = if (visible) View.VISIBLE else View.GONE
|
||||
showing = visible
|
||||
if (show != polling) {
|
||||
polling = show
|
||||
if (show) Choreographer.getInstance().postFrameCallback(frameCallback)
|
||||
@@ -208,14 +214,20 @@ class DroidDeckHudHost(context: Context) {
|
||||
file
|
||||
}.getOrNull()
|
||||
|
||||
private fun sessionResolution(): String {
|
||||
val (width, height) = SessionState.outputSize
|
||||
return "${width}x${height}"
|
||||
}
|
||||
|
||||
private fun refreshLabels(hud: DroidDeckHudView) {
|
||||
if (feed.engine.isNotBlank() && !feed.steamFocused) engine = feed.engine
|
||||
hud.setEngineLabel(engine)
|
||||
if (gpuName.isNotBlank()) hud.setGpuModel(gpuName)
|
||||
hud.setResolution(if (feed.outputWidth > 0 && feed.outputHeight > 0) buildString {
|
||||
val feedResolution = if (feed.outputWidth > 0 && feed.outputHeight > 0) buildString {
|
||||
append(feed.outputWidth).append('x').append(feed.outputHeight)
|
||||
if (feed.refreshMilliHz > 0) append(" @ ").append((feed.refreshMilliHz + 499) / 1000).append("Hz")
|
||||
} else "")
|
||||
} else ""
|
||||
hud.setResolution(feedResolution.ifBlank { sessionResolution() })
|
||||
val pid = feed.pid
|
||||
if (pid > 0 && pid != protonPid) {
|
||||
protonPid = pid
|
||||
|
||||
@@ -698,6 +698,9 @@ class DroidDeckHudView(context: Context) : View(context) {
|
||||
contentW = max(contentW, pad + w + pad)
|
||||
}
|
||||
|
||||
private fun hasGpuStats(s: HudMetrics.Snapshot): Boolean =
|
||||
s.gpuPercent != null || s.gpuTempC != null || s.gpuClockMhz != null
|
||||
|
||||
private fun buildFull(s: HudMetrics.Snapshot) {
|
||||
val rowPx = sp(12f); val unitPx = rowPx * 0.62f
|
||||
val pad = sp(10f); val lineGap = sp(4f); val lvGap = sp(8f)
|
||||
@@ -709,7 +712,7 @@ class DroidDeckHudView(context: Context) : View(context) {
|
||||
rows.add(HudRow(Span("DISP", colDisp, rowPx), listOf(Span(displayServer, colValue, rowPx))))
|
||||
if (hdrState != DroidDeckHudHdr.NONE)
|
||||
rows.add(HudRow(Span("HDR", colHdr, rowPx), hdrValue(rowPx)))
|
||||
if (showGPU) {
|
||||
if (showGPU && hasGpuStats(s)) {
|
||||
val v = ArrayList<Span>()
|
||||
v += numUnit(s.gpuPercent?.toString(), "%", rowPx, unitPx)
|
||||
if (showGpuTemp) tempSpans(s.gpuTempC, HudMetrics.TempSensor.GPU, rowPx, unitPx)
|
||||
@@ -720,9 +723,9 @@ class DroidDeckHudView(context: Context) : View(context) {
|
||||
if (showCPU) {
|
||||
val v = ArrayList<Span>()
|
||||
v += numUnit(s.cpuPercent?.toString(), "%", rowPx, unitPx)
|
||||
v += gap(unitPx); v += numUnit(s.cpuClockMhz?.toString(), "MHz", rowPx, unitPx)
|
||||
if (showCpuTemp) tempSpans(s.cpuTempC, HudMetrics.TempSensor.CPU, rowPx, unitPx)
|
||||
.let { if (it.isNotEmpty()) { v += gap(unitPx); v += it } }
|
||||
v += gap(unitPx); v += numUnit(s.cpuClockMhz?.toString(), "MHz", rowPx, unitPx)
|
||||
rows.add(HudRow(Span("CPU", colCpu, rowPx), v))
|
||||
}
|
||||
if (showVram && s.vramText() != null)
|
||||
@@ -799,12 +802,13 @@ class DroidDeckHudView(context: Context) : View(context) {
|
||||
"${fmt1(fpsAvg)} avg · ${lowText(lows.low1Fps) ?: "—"} 1%", false))
|
||||
if (showFPS) tiles.add(Tile("FRAME", colDim, numUnit(fmt1(1000f / max(fpsNow, 1f)), "ms", valPx, unitPx),
|
||||
"${fmt1(graphMinMs)} – ${fmt1(graphMaxMs)}", false))
|
||||
if (showGPU) tiles.add(Tile("GPU", colGpu, numUnit(s.gpuPercent?.toString(), "%", valPx, unitPx),
|
||||
if (showGPU && hasGpuStats(s)) tiles.add(Tile("GPU", colGpu, numUnit(s.gpuPercent?.toString(), "%", valPx, unitPx),
|
||||
if (showGpuTemp && s.gpuTempC != null) HudMetrics.formatTemp(s.gpuTempC.toFloat(), tempDisplay, false)
|
||||
else s.gpuClockMhz?.let { "${it}MHz" }, false))
|
||||
if (showCPU) tiles.add(Tile("CPU", colCpu, numUnit(s.cpuPercent?.toString(), "%", valPx, unitPx),
|
||||
listOfNotNull(if (showCpuTemp) s.cpuTempC?.let { HudMetrics.formatTemp(it.toFloat(), tempDisplay, false) } else null,
|
||||
s.cpuClockMhz?.toString()).joinToString(" · ").ifBlank { null }, false))
|
||||
listOfNotNull(s.cpuClockMhz?.let { "${it}MHz" },
|
||||
if (showCpuTemp) s.cpuTempC?.let { HudMetrics.formatTemp(it.toFloat(), tempDisplay, false) } else null)
|
||||
.joinToString(" · ").ifBlank { null }, false))
|
||||
if (showVram && s.vramText() != null) tiles.add(Tile("VRAM", colVram, valueUnit(s.vramText()!!, valPx, unitPx), null, false))
|
||||
if (showRAM) tiles.add(Tile("RAM", colRam, numUnit(s.ramPercent.roundToInt().toString(), "%", valPx, unitPx),
|
||||
"${s.ramUsedText()} / ${s.ramTotalText()}", false))
|
||||
@@ -881,7 +885,7 @@ class DroidDeckHudView(context: Context) : View(context) {
|
||||
val gpuName = if (showGpuModel && gpuNameText().isNotBlank()) gpuModelSpans(stkPx, colDim, compact = true) else null
|
||||
run {
|
||||
val l = ArrayList<Span>()
|
||||
if (showGPU) { l += Span("GPU ${s.gpuPercent ?: "—"}%", colGpu, stkPx) }
|
||||
if (showGPU && s.gpuPercent != null) { l += Span("GPU ${s.gpuPercent}%", colGpu, stkPx) }
|
||||
if (showCPU) { if (l.isNotEmpty()) l += Span(" · ", colDim, stkPx); l += Span("CPU ${s.cpuPercent ?: "—"}%", colCpu, stkPx) }
|
||||
if (l.isNotEmpty()) stack.add(l)
|
||||
}
|
||||
@@ -1137,7 +1141,7 @@ class DroidDeckHudView(context: Context) : View(context) {
|
||||
val left = ArrayList<HudRow>()
|
||||
if (showGpuModel && gpuNameText().isNotBlank())
|
||||
left.add(HudRow(Span("GPU", colGpu, rowPx), gpuModelSpans(rowPx, colValue)))
|
||||
if (showGPU) {
|
||||
if (showGPU && hasGpuStats(s)) {
|
||||
val v = ArrayList<Span>()
|
||||
v += numUnit(s.gpuPercent?.toString(), "%", rowPx, unitPx)
|
||||
if (showGpuTemp) tempSpans(s.gpuTempC, HudMetrics.TempSensor.GPU, rowPx, unitPx)
|
||||
@@ -1148,9 +1152,9 @@ class DroidDeckHudView(context: Context) : View(context) {
|
||||
if (showCPU) {
|
||||
val v = ArrayList<Span>()
|
||||
v += numUnit(s.cpuPercent?.toString(), "%", rowPx, unitPx)
|
||||
v += gap(unitPx); v += numUnit(s.cpuClockMhz?.toString(), "MHz", rowPx, unitPx)
|
||||
if (showCpuTemp) tempSpans(s.cpuTempC, HudMetrics.TempSensor.CPU, rowPx, unitPx)
|
||||
.let { if (it.isNotEmpty()) { v += gap(unitPx); v += it } }
|
||||
v += gap(unitPx); v += numUnit(s.cpuClockMhz?.toString(), "MHz", rowPx, unitPx)
|
||||
left.add(HudRow(Span("CPU", colCpu, rowPx), v))
|
||||
}
|
||||
if (showPerCore) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.droiddeck.launcher.hud
|
||||
|
||||
import android.os.Environment
|
||||
import com.droiddeck.launcher.runtime.LinuxRuntime
|
||||
import java.io.File
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
internal class MangoReadings(private val sessionDir: File, private val rootDir: File) {
|
||||
class Cpu(val percent: Int, val perCore: IntArray)
|
||||
@@ -48,6 +50,7 @@ internal class MangoReadings(private val sessionDir: File, private val rootDir:
|
||||
}
|
||||
|
||||
fun cpuTempC(): Int? {
|
||||
externalCpuTemp()?.let { return it }
|
||||
var sum = 0f
|
||||
var count = 0
|
||||
for (zone in LinuxRuntime.cpuTempZones()) {
|
||||
@@ -58,6 +61,13 @@ internal class MangoReadings(private val sessionDir: File, private val rootDir:
|
||||
return if (count == 0) null else (sum / count).toInt()
|
||||
}
|
||||
|
||||
/** Optional ADB helper feed: /sdcard/DroidDeck/cpu-temp, written from the host while a session runs. */
|
||||
private fun externalCpuTemp(): Int? {
|
||||
val file = File(Environment.getExternalStorageDirectory(), "DroidDeck/cpu-temp")
|
||||
if (!file.isFile || System.currentTimeMillis() - file.lastModified() > CPU_TEMP_FRESH_MS) return null
|
||||
return readFloat(file)?.takeIf { it.isFinite() && it in -20f..150f }?.roundToInt()
|
||||
}
|
||||
|
||||
fun gpuPercent(): Int? {
|
||||
val feed = fresh(File(sessionDir, "sys/kgsl-3d0/perf_now"))
|
||||
val source = feed ?: LinuxRuntime.gpuLoadSource()?.let(::File) ?: return null
|
||||
@@ -92,7 +102,8 @@ internal class MangoReadings(private val sessionDir: File, private val rootDir:
|
||||
return Memory((total - available) * 1024, total * 1024, (swapTotal - swapFree) * 1024, swapTotal * 1024)
|
||||
}
|
||||
|
||||
fun vramBytes(): Long? = fresh(File(rootDir, VRAM_FILE), VRAM_FRESH_MS)?.let(::readLong)
|
||||
/** The KGSL total DroidDeck shares with the session (GpuMemComponent). */
|
||||
fun vramBytes(): Long? = fresh(File(rootDir, VRAM_FILE), VRAM_FRESH_MS)?.let(::readLong)?.takeIf { it > 0 }
|
||||
|
||||
fun battery(): Battery? {
|
||||
val dirs = File(sessionDir, "sys/power_supply").listFiles { f -> f.name.contains("BAT") }?.sortedBy { it.name }
|
||||
@@ -132,6 +143,7 @@ internal class MangoReadings(private val sessionDir: File, private val rootDir:
|
||||
const val VRAM_FILE = "run/droiddeck-hud/gpu-mem"
|
||||
private const val FEED_FRESH_MS = 10_000L
|
||||
private const val VRAM_FRESH_MS = 15_000L
|
||||
private const val CPU_TEMP_FRESH_MS = 10_000L
|
||||
private val WHITESPACE = Regex("\\s+")
|
||||
|
||||
private fun readLong(file: File): Long? = runCatching { file.readText().trim().toLong() }.getOrNull()
|
||||
|
||||
@@ -200,6 +200,7 @@ object BwrapSpawner {
|
||||
LIB_DIRS.map { File(rootfs, "$it/$name") }.firstOrNull { it.exists() }
|
||||
|
||||
private fun gpu(rootfs: String, root: File, env: MutableMap<String, String>): List<Bind>? {
|
||||
env["VKBRIDGE_SOCKET"]?.let { sock -> if (File(sock).exists()) return bridgeGpu(rootfs, root, env, sock) }
|
||||
val driver = runtimeLib(rootfs, "libvulkan_freedreno.so")
|
||||
if (driver == null || !File("/dev/kgsl-3d0").exists()) return null
|
||||
if (env.containsKey("VK_ICD_FILENAMES") || env.containsKey("VK_DRIVER_FILES")) return null
|
||||
@@ -222,6 +223,28 @@ object BwrapSpawner {
|
||||
return binds
|
||||
}
|
||||
|
||||
private fun bridgeGpu(rootfs: String, root: File, env: MutableMap<String, String>, socket: String): List<Bind>? {
|
||||
val driver = File(rootfs, "usr/local/lib/vkbridge/libvulkan_droidbridge.so")
|
||||
if (!driver.isFile) { Log.w(TAG, "gpu: vkbridge session but no bridge ICD in the runtime"); return null }
|
||||
val binds = ArrayList<Bind>()
|
||||
binds.add(Bind(driver.canonicalPath, "$GPU_DIR/libvulkan_droidbridge.so"))
|
||||
val sockDir = File(socket).parent ?: return null
|
||||
binds.add(Bind(sockDir, sockDir))
|
||||
val icd = File(root.parentFile, "vkbridge_icd.json")
|
||||
val api = runCatching {
|
||||
JSONObject(File(rootfs, "usr/local/share/vkbridge/droidbridge_icd.json").readText()).getJSONObject("ICD").getString("api_version")
|
||||
}.getOrNull() ?: run { Log.w(TAG, "gpu: vkbridge session but the runtime's bridge manifest is unreadable"); return null }
|
||||
icd.writeText("{\"file_format_version\": \"1.0.0\", \"ICD\": {\"library_path\": \"$GPU_DIR/libvulkan_droidbridge.so\", \"api_version\": \"$api\"}}\n")
|
||||
binds.add(Bind(icd.path, "$GPU_DIR/vkbridge_icd.json"))
|
||||
env["VK_DRIVER_FILES"] = "$GPU_DIR/vkbridge_icd.json"
|
||||
env["VK_ICD_FILENAMES"] = "$GPU_DIR/vkbridge_icd.json"
|
||||
if (!env.containsKey("MESA_LOADER_DRIVER_OVERRIDE") && !env.containsKey("GALLIUM_DRIVER")) {
|
||||
env["MESA_LOADER_DRIVER_OVERRIDE"] = "zink"
|
||||
env["GALLIUM_DRIVER"] = "zink"
|
||||
}
|
||||
return binds
|
||||
}
|
||||
|
||||
private fun preload(session: List<Bind>, rootfs: String, env: MutableMap<String, String>): List<Bind> {
|
||||
val libs = FileUtils.readString(File(rootfs, "etc/ld.so.preload"))?.lines()
|
||||
?.map { it.trim() }?.filter { it.startsWith("/") && File(rootfs + it).isFile } ?: return emptyList()
|
||||
|
||||
@@ -251,12 +251,36 @@ class LinuxNetworkLinkComponent(
|
||||
}
|
||||
}
|
||||
|
||||
private fun localDnsServers(): Pair<List<String>, List<String>> {
|
||||
val active = connectivity.activeNetwork?.let { connectivity.getNetworkCapabilities(it) }
|
||||
if (active?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true) return Pair(emptyList(), emptyList())
|
||||
val servers = LinkedHashSet<String>()
|
||||
val gateways = LinkedHashSet<String>()
|
||||
for (network in connectivity.allNetworks) {
|
||||
val capabilities = connectivity.getNetworkCapabilities(network) ?: continue
|
||||
if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) continue
|
||||
if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
|
||||
!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) continue
|
||||
val link = connectivity.getLinkProperties(network) ?: continue
|
||||
for (address in link.dnsServers) {
|
||||
if (address.isLinkLocalAddress || address.isLoopbackAddress || address.isAnyLocalAddress) continue
|
||||
address.hostAddress?.substringBefore('%')?.let { servers.add(it) }
|
||||
}
|
||||
link.routes.firstOrNull {
|
||||
it.isDefaultRoute && (it.gateway as? Inet4Address)?.let { g -> !g.isLoopbackAddress && !g.isAnyLocalAddress } == true
|
||||
}?.gateway?.hostAddress?.substringBefore('%')?.let { gateways.add(it) }
|
||||
}
|
||||
val order = compareBy<String>({ if (it.contains(':')) 1 else 0 })
|
||||
return Pair(servers.sortedWith(order), gateways.sortedWith(order))
|
||||
}
|
||||
|
||||
private fun writeResolver(properties: LinkProperties?) {
|
||||
val own = properties?.dnsServers.orEmpty()
|
||||
.filter { !it.isLinkLocalAddress && !it.isLoopbackAddress && !it.isAnyLocalAddress }
|
||||
.sortedBy { if (it is Inet4Address) 0 else 1 }
|
||||
.mapNotNull { it.hostAddress?.substringBefore('%') }
|
||||
val servers = (own + listOf("8.8.8.8", "1.1.1.1", "2001:4860:4860::8888")).distinct().take(6)
|
||||
val (discovered, gateways) = localDnsServers()
|
||||
val servers = (own + discovered + listOf("8.8.8.8", "1.1.1.1", "2001:4860:4860::8888") + gateways).distinct().take(6)
|
||||
val text = buildString {
|
||||
append("# Written by the app from the device's active network; edits are overwritten.\n")
|
||||
for (server in servers) append("nameserver $server\n")
|
||||
|
||||
@@ -278,10 +278,42 @@ public final class LinuxRuntime {
|
||||
return false;
|
||||
}
|
||||
|
||||
static final String MALI_DEVICE = "/dev/mali0";
|
||||
|
||||
static final String[] DRM_RENDER_NODES = {
|
||||
"/dev/dri/renderD128", "/dev/dri/renderD129", "/dev/dri/renderD130", "/dev/dri/renderD131",
|
||||
};
|
||||
|
||||
public static String gpuStandIn(Context context) {
|
||||
if (new File(KGSL_DEVICE).exists()) return KGSL_DEVICE;
|
||||
if (!com.droiddeck.launcher.core.DeviceSupport.INSTANCE.vkBridge(context)) return null;
|
||||
return bridgeStandIn(path -> new File(path).exists());
|
||||
}
|
||||
|
||||
static String bridgeStandIn(java.util.function.Predicate<String> exists) {
|
||||
if (exists.test(MALI_DEVICE)) return MALI_DEVICE;
|
||||
for (String node : DRM_RENDER_NODES) if (exists.test(node)) return node;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String deviceNumbers(String path) {
|
||||
try {
|
||||
long dev = Os.stat(path).st_rdev;
|
||||
long major = ((dev >> 8) & 0xfff) | ((dev >> 32) & ~0xfffL);
|
||||
long minor = (dev & 0xff) | ((dev >> 12) & ~0xffL);
|
||||
return major + ":" + minor;
|
||||
} catch (ErrnoException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void bindGpuNode(Context context, List<String> cmd) {
|
||||
String standIn = gpuStandIn(context);
|
||||
if (standIn == null) return;
|
||||
boolean kgsl = standIn.equals(KGSL_DEVICE);
|
||||
StructStat st;
|
||||
try {
|
||||
st = Os.stat(KGSL_DEVICE);
|
||||
st = Os.stat(standIn);
|
||||
} catch (ErrnoException e) {
|
||||
return;
|
||||
}
|
||||
@@ -301,13 +333,14 @@ public final class LinuxRuntime {
|
||||
Files.write(new File(drm, "dev").toPath(),
|
||||
(major + ":" + minor + "\n").getBytes(StandardCharsets.UTF_8));
|
||||
Files.write(new File(device, "uevent").toPath(),
|
||||
"DRIVER=kgsl-3d0\nMODALIAS=platform:kgsl-3d0\n".getBytes(StandardCharsets.UTF_8));
|
||||
(kgsl ? "DRIVER=kgsl-3d0\nMODALIAS=platform:kgsl-3d0\n" : "DRIVER=mali\nMODALIAS=platform:mali\n")
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
File subsystem = new File(device, "subsystem");
|
||||
if (!Files.isSymbolicLink(subsystem.toPath())) {
|
||||
Os.symlink("/sys/bus/platform", subsystem.getPath());
|
||||
}
|
||||
File driver = new File(device, "driver");
|
||||
if (!Files.isSymbolicLink(driver.toPath())) {
|
||||
if (kgsl && !Files.isSymbolicLink(driver.toPath())) {
|
||||
Os.symlink("/sys/bus/platform/drivers/msm_drm", driver.getPath());
|
||||
}
|
||||
} catch (IOException | ErrnoException e) {
|
||||
@@ -315,7 +348,7 @@ public final class LinuxRuntime {
|
||||
}
|
||||
bind(cmd, new File(base, "sys").getPath() + ":/sys/dev/char");
|
||||
bind(cmd, dri.getPath() + ":/dev/dri");
|
||||
bind(cmd, KGSL_DEVICE + ":/dev/dri/" + node);
|
||||
bind(cmd, standIn + ":/dev/dri/" + node);
|
||||
bindDrmClass(base, cmd, node, major + ":" + minor);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.util.Log
|
||||
import com.droiddeck.launcher.core.Downloader
|
||||
import com.droiddeck.launcher.core.FileUtils
|
||||
import com.droiddeck.launcher.core.GameEnvironment
|
||||
import com.droiddeck.launcher.gpu.AppVulkan
|
||||
import com.droiddeck.launcher.gpu.GpuInfo
|
||||
import com.droiddeck.launcher.runtime.LinuxRuntime
|
||||
import com.github.luben.zstd.ZstdInputStream
|
||||
@@ -638,29 +639,31 @@ object ComponentsManager {
|
||||
}
|
||||
|
||||
|
||||
fun swap(context: Context, protonId: String, file: String): String = synchronized(lock) {
|
||||
fun swap(context: Context, protonId: String, file: String, auto: Boolean = false): String = synchronized(lock) {
|
||||
val p = proton(context, protonId)
|
||||
val wcp = File(packagesDir(context), safeName(file))
|
||||
val info = packageInfo(context, wcp)
|
||||
val state = loadState(context)
|
||||
if (inUse(context, p)) {
|
||||
state.sub("queued").sub(p.id).put(info.comp, JSONObject().put("kind", "package").put("file", wcp.name).put("label", info.version))
|
||||
state.sub("queued").sub(p.id).put(info.comp, JSONObject().put("kind", "package").put("file", wcp.name).put("label", info.version)
|
||||
.apply { if (auto) put("auto", true) })
|
||||
saveState(context, state)
|
||||
return context.getString(R.string.cmgr_swap_queued, LABEL[info.comp], info.version, p.name)
|
||||
}
|
||||
applyPackage(context, p, info, wcp, state)
|
||||
applyPackage(context, p, info, wcp, state, auto)
|
||||
state.sub("queued").optJSONObject(p.id)?.remove(info.comp)
|
||||
saveState(context, state)
|
||||
context.getString(R.string.cmgr_swap_done, LABEL[info.comp], info.version, p.name)
|
||||
}
|
||||
|
||||
private fun applyPackage(context: Context, p: Proton, info: Package, wcp: File, state: JSONObject) {
|
||||
private fun applyPackage(context: Context, p: Proton, info: Package, wcp: File, state: JSONObject, auto: Boolean = false) {
|
||||
ensureOriginal(context, p, info.comp, state)
|
||||
val hashes = installFiles(context, p.dir, info.comp, wcp)
|
||||
state.sub("active").sub(p.id).put(info.comp, JSONObject()
|
||||
.put("file", wcp.name).put("label", info.version).put("protonVersion", p.version)
|
||||
.put("files", JSONObject(hashes.filterKeys { owned(info.comp, it) } as Map<*, *>))
|
||||
.put("at", System.currentTimeMillis() / 1000))
|
||||
.put("at", System.currentTimeMillis() / 1000)
|
||||
.apply { if (auto) put("auto", true) })
|
||||
Log.i(TAG, "swapped ${info.comp} ${info.version} into ${p.name} ${p.version}")
|
||||
}
|
||||
|
||||
@@ -708,7 +711,7 @@ object ComponentsManager {
|
||||
applyOriginal(context, p, comp, v, File(originalsDir(context), "$pid/${safeName(v)}/$comp.wcp"), state)
|
||||
} else {
|
||||
val wcp = File(packagesDir(context), q.optString("file"))
|
||||
applyPackage(context, p, packageInfo(context, wcp), wcp, state)
|
||||
applyPackage(context, p, packageInfo(context, wcp), wcp, state, q.optBoolean("auto"))
|
||||
}
|
||||
done += "${LABEL[comp]} ${q.optString("label")} → ${p.name}"
|
||||
}.onFailure { Log.w(TAG, "queued $comp for ${p.name}", it) }
|
||||
@@ -1011,15 +1014,76 @@ object ComponentsManager {
|
||||
internal fun forcedPackageWanted(activeFile: String?, protonVersionMatches: Boolean): Boolean =
|
||||
activeFile.isNullOrEmpty() || !protonVersionMatches
|
||||
|
||||
internal fun bridgeDxvk(vk: AppVulkan?): ForcedPackage? = when {
|
||||
vk == null -> null
|
||||
!vk.atLeast(1, 1) -> null
|
||||
!vk.atLeast(1, 3) -> ForcedPackage(emptySet(), "dxvk", DXVK_FOR_VULKAN_1_1, "Dxvk-Linux")
|
||||
vk.shaderInt64 == false || (vk.maxPushConstants ?: 256) < 256 ->
|
||||
ForcedPackage(emptySet(), "dxvk", DXVK_FOR_VULKAN_1_3_NO_INT64, "Dxvk-Linux")
|
||||
else -> null
|
||||
}
|
||||
|
||||
internal const val DXVK_FOR_VULKAN_1_1 = "dxvk-1.10.3-linux.wcp"
|
||||
internal const val DXVK_FOR_VULKAN_1_3_NO_INT64 = "dxvk-2.6.2-linux.wcp"
|
||||
|
||||
internal fun autoDxvkUndoWanted(bridgeSession: Boolean, keepProtonDxvk: Boolean, vulkan: AppVulkan?, bridgeRule: ForcedPackage?): Boolean =
|
||||
!bridgeSession || keepProtonDxvk || (vulkan != null && bridgeRule == null)
|
||||
|
||||
private fun dropQueuedAuto(context: Context, installed: List<Proton>) = synchronized(lock) {
|
||||
val state = loadState(context)
|
||||
var changed = false
|
||||
for (p in installed) {
|
||||
val q = state.optJSONObject("queued")?.optJSONObject(p.id) ?: continue
|
||||
if (q.optJSONObject("dxvk")?.optBoolean("auto") == true) { q.remove("dxvk"); changed = true }
|
||||
}
|
||||
if (changed) saveState(context, state)
|
||||
}
|
||||
|
||||
internal fun autoChoiceStale(entry: JSONObject?, wantedFile: String?): Boolean =
|
||||
entry != null && entry.optBoolean("auto") && entry.optString("file") != wantedFile
|
||||
|
||||
internal fun catalogMatch(items: List<CatalogItem>, file: String, release: String): CatalogItem? =
|
||||
items.firstOrNull { it.file == file && it.release == release }
|
||||
|
||||
fun ensureForcedPackages(context: Context): String? = ensureForcedPackages(
|
||||
private val forcedLock = Any()
|
||||
|
||||
fun ensureForcedPackages(context: Context): String? = synchronized(forcedLock) { ensureForcedPackages(
|
||||
context, GpuInfo.detect(),
|
||||
{ file, release -> catalogItem(context, file, release) },
|
||||
{ download(context, it) {} },
|
||||
{ id, file -> swap(context, id, file) },
|
||||
)
|
||||
vulkan = AppVulkan.detect(context),
|
||||
bridgeSession = com.droiddeck.launcher.core.DeviceSupport.vkBridge(context),
|
||||
applyAuto = { id, file -> swap(context, id, file, auto = true) },
|
||||
undoAuto = { id, comp -> undoAutoChoice(context, id, comp) },
|
||||
keepProtonDxvk = SessionPrefs.bridgeProtonDxvk(context),
|
||||
autoPossible = !com.droiddeck.launcher.core.DeviceSupport.adreno(),
|
||||
) }
|
||||
|
||||
fun settleAutoDxvk(context: Context) {
|
||||
val app = context.applicationContext
|
||||
Thread({
|
||||
runCatching { ensureForcedPackages(app) }
|
||||
.onSuccess { if (it != null) Log.i(TAG, it) }
|
||||
.onFailure { Log.w(TAG, "automatic DXVK", it) }
|
||||
}, "auto-dxvk").start()
|
||||
}
|
||||
|
||||
internal fun undoAutoChoice(context: Context, protonId: String, comp: String): String = synchronized(lock) {
|
||||
val p = proton(context, protonId)
|
||||
val state = loadState(context)
|
||||
val entry = state.sub("active").optJSONObject(p.id)?.optJSONObject(comp)
|
||||
val sameProton = entry?.optString("protonVersion") == p.version
|
||||
if (sameProton && originalFile(context, p, comp).isFile)
|
||||
return restore(context, protonId, comp, safeName(p.version))
|
||||
if (sameProton) {
|
||||
Log.w(TAG, "${p.name}: its own ${LABEL[comp]} copy is gone; keeping ${entry?.optString("label")}")
|
||||
return "${LABEL[comp]}: ${p.name} keeps ${entry?.optString("label")} (its own copy is gone)"
|
||||
}
|
||||
state.sub("active").optJSONObject(p.id)?.remove(comp)
|
||||
saveState(context, state)
|
||||
"${LABEL[comp]}: ${p.name} keeps its own copy"
|
||||
}
|
||||
|
||||
internal fun ensureForcedPackages(
|
||||
context: Context,
|
||||
@@ -1027,16 +1091,35 @@ object ComponentsManager {
|
||||
lookup: (file: String, release: String) -> CatalogItem?,
|
||||
fetch: (CatalogItem) -> Unit,
|
||||
apply: (protonId: String, file: String) -> String,
|
||||
vulkan: AppVulkan? = null,
|
||||
bridgeSession: Boolean = false,
|
||||
applyAuto: (protonId: String, file: String) -> String = apply,
|
||||
undoAuto: (protonId: String, comp: String) -> String = { _, _ -> "" },
|
||||
keepProtonDxvk: Boolean = false,
|
||||
autoPossible: Boolean = true,
|
||||
): String? {
|
||||
val rules = FORCED_PACKAGES.filter { it.matches(gpu) }
|
||||
if (rules.isEmpty()) return null
|
||||
val chooseDxvk = bridgeSession && !keepProtonDxvk
|
||||
val bridgeRule = if (chooseDxvk) bridgeDxvk(vulkan) else null
|
||||
val rules = FORCED_PACKAGES.filter { it.matches(gpu) }.map { it to false } + listOfNotNull(bridgeRule?.let { it to true })
|
||||
if (rules.isEmpty() && !autoPossible) return null
|
||||
val installed = protons(context)
|
||||
val lines = mutableListOf<String>()
|
||||
for (rule in rules) {
|
||||
fun each(p: Proton, what: () -> String): String =
|
||||
runCatching(what).getOrElse { e -> Log.w(TAG, "${p.name}: ${e.message}", e); "${p.name}: ${e.message ?: e.javaClass.simpleName}" }
|
||||
if (autoPossible && autoDxvkUndoWanted(bridgeSession, keepProtonDxvk, vulkan, bridgeRule)) {
|
||||
dropQueuedAuto(context, installed)
|
||||
val state = loadState(context)
|
||||
for (p in installed) {
|
||||
val entry = state.optJSONObject("active")?.optJSONObject(p.id)?.optJSONObject("dxvk")
|
||||
if (autoChoiceStale(entry, null)) lines.add(each(p) { undoAuto(p.id, "dxvk") })
|
||||
}
|
||||
}
|
||||
for ((rule, auto) in rules) {
|
||||
val state = loadState(context)
|
||||
val targets = installed.filter { p ->
|
||||
val active = state.optJSONObject("active")?.optJSONObject(p.id)?.optJSONObject(rule.comp)
|
||||
forcedPackageWanted(active?.optString("file"), active?.optString("protonVersion") == p.version)
|
||||
forcedPackageWanted(active?.optString("file"), active?.optString("protonVersion") == p.version) ||
|
||||
(auto && autoChoiceStale(active, rule.file))
|
||||
}
|
||||
if (targets.isEmpty()) continue
|
||||
val missing = missingPackage(context, rule, lookup, fetch)
|
||||
@@ -1044,9 +1127,10 @@ object ComponentsManager {
|
||||
lines.add(missing)
|
||||
continue
|
||||
}
|
||||
lines.add(targets.joinToString("; ") { apply(it.id, rule.file) })
|
||||
if (auto) lines.add("${vulkan?.label}: DXVK ${rule.file}")
|
||||
lines.add(targets.joinToString("; ") { p -> if (auto) each(p) { applyAuto(p.id, rule.file) } else apply(p.id, rule.file) })
|
||||
}
|
||||
return lines.joinToString("; ").ifEmpty { null }
|
||||
return lines.filter { it.isNotEmpty() }.joinToString("; ").ifEmpty { null }
|
||||
}
|
||||
|
||||
private fun missingPackage(
|
||||
|
||||
@@ -171,6 +171,9 @@ object SessionFiles {
|
||||
val optional = (arrayOf(
|
||||
"usr/local/bin/gamescope" to "usr/local/bin/gamescope",
|
||||
"usr/lib/libVkLayer_FROG_gamescope_wsi_aarch64.so" to "usr/lib/libVkLayer_FROG_gamescope_wsi_aarch64.so",
|
||||
"usr/local/lib/vkbridge/libvulkan_droidbridge.so" to "usr/local/lib/vkbridge/libvulkan_droidbridge.so",
|
||||
"usr/local/share/vkbridge/droidbridge_icd.json" to "usr/local/share/vkbridge/droidbridge_icd.json",
|
||||
"usr/local/bin/droiddeck-vkbridge-smoke" to "usr/local/bin/droiddeck-vkbridge-smoke",
|
||||
"usr/local/lib/droiddeck/uruntime" to "usr/local/lib/droiddeck/uruntime",
|
||||
"usr/local/lib/droiddeck-pipewire.tzst" to "usr/local/lib/droiddeck-pipewire.tzst",
|
||||
"usr/local/share/licenses/uruntime/LICENSE" to "usr/local/share/licenses/uruntime/LICENSE",
|
||||
|
||||
@@ -746,6 +746,29 @@ object SessionPrefs {
|
||||
|
||||
fun zinkLazy(context: Context): Boolean = prefs(context).getBoolean("zinkLazy", true)
|
||||
|
||||
@JvmStatic
|
||||
fun vkBridge(context: Context): Boolean = prefs(context).getBoolean("vkBridge", com.droiddeck.launcher.core.DeviceSupport.mali())
|
||||
|
||||
fun setVkBridge(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("vkBridge", on).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun bridgeProtonDxvk(context: Context): Boolean = prefs(context).getBoolean("bridgeProtonDxvk", false)
|
||||
|
||||
fun setBridgeProtonDxvk(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("bridgeProtonDxvk", on).apply()
|
||||
}
|
||||
|
||||
fun vkBridgeSafe(context: Context): Boolean = prefs(context).getBoolean("vkBridgeSafe", true)
|
||||
|
||||
fun setVkBridgeSafe(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("vkBridgeSafe", on).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun vkBridgeMode(context: Context): String = if (vkBridgeSafe(context)) "safe" else "performance"
|
||||
|
||||
fun setZinkLazy(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("zinkLazy", on).apply()
|
||||
}
|
||||
|
||||
@@ -523,6 +523,7 @@ class SessionService : Service() {
|
||||
components.add(encoder)
|
||||
guest.add("BL_VENC_SOCKET=" + encoder.socket.absolutePath)
|
||||
}
|
||||
startVkBridge(guest, sessionDir)
|
||||
|
||||
guest.add("BL_WIDTH=" + size.first)
|
||||
guest.add("BL_HEIGHT=" + size.second)
|
||||
@@ -812,7 +813,7 @@ class SessionService : Service() {
|
||||
?: SessionPrefs.androidDriver(this),
|
||||
linuxDriverId,
|
||||
)
|
||||
LinuxVulkanDriver.resolveIcdPath(this, linuxDriverId)
|
||||
if (!com.droiddeck.launcher.core.DeviceSupport.vkBridge(this)) LinuxVulkanDriver.resolveIcdPath(this, linuxDriverId)
|
||||
?.let { guest.add(LinuxVulkanDriver.ENV + "=" + it) }
|
||||
val tuOverride = tuDebugOverride()
|
||||
val tuDriver = sysmemDriver(linuxDriverId)
|
||||
@@ -867,6 +868,27 @@ class SessionService : Service() {
|
||||
private fun noSteamGame(): Boolean =
|
||||
SessionState.mode == MODE_RUN && SessionState.program in setOf(NoSteamLaunch.PROTON_RUN, NoSteamLaunch.EPIC, NoSteamLaunch.BATTLENET)
|
||||
|
||||
private fun startVkBridge(guest: MutableList<String>, sessionDir: File) {
|
||||
if (!com.droiddeck.launcher.core.DeviceSupport.vkBridge(this)) return
|
||||
val socket = File(filesDir, "vkbridge/server.sock")
|
||||
val bridge = com.droiddeck.launcher.gpu.VkBridgeComponent(socket, File(sessionDir, "vkbridge-server.log"))
|
||||
bridge.attach(this)
|
||||
components.add(bridge)
|
||||
com.droiddeck.launcher.gpu.ClockLogComponent(File(sessionDir, "vkbridge-clocks.log")).let {
|
||||
it.attach(this)
|
||||
components.add(it)
|
||||
}
|
||||
guest.add("BL_VKBRIDGE=1")
|
||||
guest.add("VKBRIDGE_SOCKET=" + socket.path)
|
||||
guest.add("VKBRIDGE_CLIENT_LOG=" + File(sessionDir, "vkbridge-client.log").path)
|
||||
guest.add("VKBRIDGE_MODE=" + SessionPrefs.vkBridgeMode(this))
|
||||
LinuxRuntime.gpuStandIn(this)?.let { node ->
|
||||
guest.add("BL_DRM_STANDIN=$node")
|
||||
LinuxRuntime.deviceNumbers(node)?.let { guest.add("VKBRIDGE_DRM_RENDER=$it") }
|
||||
}
|
||||
Log.i(TAG, "vkbridge: on (${com.droiddeck.launcher.core.DeviceSupport.gpuName(this)}, ${SessionPrefs.vkBridgeMode(this)} mode), socket $socket")
|
||||
}
|
||||
|
||||
private fun startAudio(guest: MutableList<String>, sessionDir: File, directAudioPack: Boolean): PulseAudioComponent {
|
||||
val wantsDirectAudio = directAudioPack &&
|
||||
(SessionState.mode == MODE_STEAM || SessionState.mode == MODE_DESKTOP || noSteamGame()) &&
|
||||
|
||||
@@ -149,6 +149,10 @@ class FrontEndState(
|
||||
val oscMode: String = SessionPrefs.OSC_AUTO,
|
||||
val controller: com.droiddeck.launcher.input.ControllerPrefs.Settings? = null,
|
||||
val phantomProcessStatus: PhantomProcessStatus = PhantomProcessStatus.NOT_APPLICABLE,
|
||||
val adreno: Boolean = true,
|
||||
val vkBridge: Boolean = false,
|
||||
val vkBridgeSafe: Boolean = true,
|
||||
val bridgeProtonDxvk: Boolean = false,
|
||||
val showPhantomGate: Boolean = false,
|
||||
val orientation: Int = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
||||
val launcherOrientation: Int = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER,
|
||||
@@ -205,6 +209,9 @@ class FrontEndActions(
|
||||
val onInstallPackage: (String) -> Unit,
|
||||
val onRemovePackage: (String) -> Unit,
|
||||
val onRuntime: () -> Unit,
|
||||
val onVkBridge: (Boolean) -> Unit = {},
|
||||
val onVkBridgeSafe: (Boolean) -> Unit = {},
|
||||
val onBridgeProtonDxvk: (Boolean) -> Unit = {},
|
||||
val onFrameGenPick: (FrameGen.Mode) -> Unit,
|
||||
val onImportLossless: () -> Unit,
|
||||
val onProtons: () -> Unit,
|
||||
|
||||
@@ -171,7 +171,7 @@ internal fun SetupPanel(
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
val gpu = remember { com.droiddeck.launcher.gpu.GpuInfo.detect() }
|
||||
val gpuOk = gpu.support == com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED
|
||||
val gpuOk = gpu.support == com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED || s.vkBridge
|
||||
val gpuName = remember { DeviceSupport.gpuName(ctx) }
|
||||
val limitBlocks = PhantomProcessLimit.blocksSteam(s.phantomProcessStatus)
|
||||
val signedIn = s.offlineAccount != null
|
||||
@@ -232,7 +232,7 @@ internal fun SetupPanel(
|
||||
val runtimeOk = s.ready && !s.busy && !s.removalPending
|
||||
val checks = listOf(gpuOk, runtimeOk, !limitBlocks, signedIn)
|
||||
val passing = checks.count { it }
|
||||
var showChecks by rememberSaveable { mutableStateOf(false) }
|
||||
var showChecks by rememberSaveable { mutableStateOf(s.vkBridge) }
|
||||
Rise(0, Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().bumpers(
|
||||
@@ -249,17 +249,49 @@ internal fun SetupPanel(
|
||||
val gpuCheck: @Composable (Boolean) -> Unit = { divider ->
|
||||
CheckRow(
|
||||
if (gpuOk) CheckState.OK else CheckState.WARN,
|
||||
when (gpu.support) {
|
||||
com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED -> stringResource(R.string.setup_gpu_supported)
|
||||
com.droiddeck.launcher.gpu.GpuInfo.Support.UNTESTED -> stringResource(R.string.setup_gpu_untested)
|
||||
when {
|
||||
gpu.support == com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED -> stringResource(R.string.setup_gpu_supported)
|
||||
gpu.support == com.droiddeck.launcher.gpu.GpuInfo.Support.UNTESTED -> stringResource(R.string.setup_gpu_untested)
|
||||
s.vkBridge -> stringResource(R.string.setup_gpu_bridged)
|
||||
else -> stringResource(R.string.setup_gpu_unsupported)
|
||||
},
|
||||
when (gpu.support) {
|
||||
com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED -> stringResource(R.string.setup_gpu_detail, gpu.displayName(ctx), gpuName)
|
||||
com.droiddeck.launcher.gpu.GpuInfo.Support.UNTESTED -> stringResource(R.string.setup_gpu_untested_detail, gpu.displayName(ctx), gpu.supportText(ctx).replaceFirstChar { it.lowercase() })
|
||||
else -> stringResource(R.string.setup_gpu_unsupported_detail, gpuName)
|
||||
else -> if (s.vkBridge) stringResource(R.string.setup_gpu_bridge_detail, gpuName)
|
||||
else stringResource(R.string.setup_gpu_unsupported_detail, gpuName)
|
||||
},
|
||||
divider = divider || (!s.adreno && s.vkBridge),
|
||||
action = if (s.adreno) null else { {
|
||||
SecondaryButton(
|
||||
stringResource(if (s.vkBridge) R.string.setup_gpu_bridge_on else R.string.setup_gpu_bridge_off),
|
||||
enabled = !s.sessionRunning, compact = true, onClick = { a.onVkBridge(!s.vkBridge) },
|
||||
)
|
||||
} },
|
||||
)
|
||||
if (!s.adreno && s.vkBridge) CheckRow(
|
||||
CheckState.OK,
|
||||
stringResource(R.string.setup_bridge_safe),
|
||||
stringResource(if (s.vkBridgeSafe) R.string.setup_bridge_safe_on_detail else R.string.setup_bridge_safe_off_detail),
|
||||
divider = divider,
|
||||
action = {
|
||||
SecondaryButton(
|
||||
stringResource(if (s.vkBridgeSafe) R.string.setup_bridge_safe_on else R.string.setup_bridge_safe_off),
|
||||
enabled = !s.sessionRunning, compact = true, onClick = { a.onVkBridgeSafe(!s.vkBridgeSafe) },
|
||||
)
|
||||
},
|
||||
)
|
||||
if (!s.adreno && s.vkBridge) CheckRow(
|
||||
CheckState.OK,
|
||||
stringResource(R.string.setup_bridge_dxvk),
|
||||
stringResource(if (s.bridgeProtonDxvk) R.string.setup_bridge_dxvk_proton_detail else R.string.setup_bridge_dxvk_auto_detail),
|
||||
divider = divider,
|
||||
action = {
|
||||
SecondaryButton(
|
||||
stringResource(if (s.bridgeProtonDxvk) R.string.setup_bridge_dxvk_proton else R.string.setup_bridge_dxvk_auto),
|
||||
enabled = !s.sessionRunning, compact = true, onClick = { a.onBridgeProtonDxvk(!s.bridgeProtonDxvk) },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
val runtimeCheck: @Composable (Boolean) -> Unit = { divider ->
|
||||
|
||||
@@ -3671,6 +3671,19 @@
|
||||
<string name="integration_shortcuts_service">زر الشاشة الرئيسية في DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">يمرّر ضغطات زر الشاشة الرئيسية إلى Steam Guide عندما تكون جلسة Steam في المقدمة ويكون DroidDeck تطبيق الشاشة الرئيسية الافتراضي. لا يقرأ محتوى الشاشة.</string>
|
||||
<string name="integration_brightness_needed">فعّل «السطوع» من الإعداد → تكامل Steam</string>
|
||||
<string name="setup_gpu_bridge_off">جسر Mali: متوقف</string>
|
||||
<string name="setup_gpu_bridge_on">جسر Mali: يعمل</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s ليس Adreno. جسر Vulkan التجريبي يعمل: تعرض الجلسة الرسومات عبر برنامج تشغيل الهاتف نفسه. توقّع وجود أخطاء؛ أرسل سجلات الجلسة.</string>
|
||||
<string name="setup_bridge_safe">الوضع الآمن</string>
|
||||
<string name="setup_bridge_safe_on">الوضع الآمن: يعمل</string>
|
||||
<string name="setup_bridge_safe_off">الوضع الآمن: متوقف</string>
|
||||
<string name="setup_bridge_safe_on_detail">الأكثر توافقًا. يوصى به لجهاز جديد.</string>
|
||||
<string name="setup_bridge_safe_off_detail">أسرع حيث اجتاز معالج الرسومات هذا فحوصاته.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: تلقائي</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: الخاص بـ Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">يستخدم إصدار DXVK أقدم عندما يكون Vulkan في معالج الرسومات هذا أقدم من أن يدعم DXVK الخاص بـ Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">يحتفظ كل Proton بـ DXVK الخاص به، حتى عندما قد يكون Vulkan في معالج الرسومات هذا أقدم من اللازم.</string>
|
||||
<string name="apps_search_hint">البحث في التطبيقات</string>
|
||||
<string name="apps_no_match">لا توجد تطبيقات تطابق ”%1$s“.</string>
|
||||
<string name="apps_add_widget">إضافة أداة</string>
|
||||
|
||||
@@ -3536,6 +3536,19 @@
|
||||
<string name="integration_shortcuts_service">Botón de inicio de DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Pasa las pulsaciones del botón de inicio a Steam Guide mientras una sesión de Steam está en primer plano y DroidDeck es la app de inicio predeterminada. No lee el contenido de la pantalla.</string>
|
||||
<string name="integration_brightness_needed">Activa Brillo en Configuración → Integración con Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Puente Mali: desactivado</string>
|
||||
<string name="setup_gpu_bridge_on">Puente Mali: activado</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s no es una Adreno. El puente Vulkan experimental está activado: la sesión dibuja a través del propio controlador del teléfono. Espera errores; envía los registros de la sesión.</string>
|
||||
<string name="setup_bridge_safe">Modo seguro</string>
|
||||
<string name="setup_bridge_safe_on">Modo seguro: activado</string>
|
||||
<string name="setup_bridge_safe_off">Modo seguro: desactivado</string>
|
||||
<string name="setup_bridge_safe_on_detail">El más compatible. Recomendado para un dispositivo nuevo.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Más rápido donde esta GPU pasó sus pruebas.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automático</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: el de Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Usa un DXVK más antiguo cuando el Vulkan de esta GPU es demasiado antiguo para el de Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Cada Proton conserva su propio DXVK, aunque el Vulkan de esta GPU pueda ser demasiado antiguo para él.</string>
|
||||
<string name="apps_search_hint">Buscar apps</string>
|
||||
<string name="apps_no_match">Ninguna app coincide con «%1$s».</string>
|
||||
<string name="apps_add_widget">Agregar widget</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">Бутон за начален екран на DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Предава натисканията на бутона за начален екран към Steam Guide, докато сесия на Steam е на фокус и DroidDeck е приложението за начален екран по подразбиране. Не чете съдържанието на екрана.</string>
|
||||
<string name="integration_brightness_needed">Включете „Яркост“ в Настройка → Интеграция със Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Mali мост: изкл.</string>
|
||||
<string name="setup_gpu_bridge_on">Mali мост: вкл.</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s не е Adreno. Експерименталният Vulkan мост е включен: сесията рисува през собствения драйвер на телефона. Очаквайте грешки; изпратете логовете на сесията.</string>
|
||||
<string name="setup_bridge_safe">Безопасен режим</string>
|
||||
<string name="setup_bridge_safe_on">Безопасен режим: вкл.</string>
|
||||
<string name="setup_bridge_safe_off">Безопасен режим: изкл.</string>
|
||||
<string name="setup_bridge_safe_on_detail">Най-съвместим. Препоръчва се за ново устройство.</string>
|
||||
<string name="setup_bridge_safe_off_detail">По-бързо там, където този GPU е минал проверките си.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: автоматично</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: на Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Използва по-стар DXVK, когато Vulkan на този GPU е твърде стар за този на Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Всеки Proton запазва своя DXVK, дори когато Vulkan на този GPU може да е твърде стар за него.</string>
|
||||
<string name="apps_search_hint">Търсене на приложения</string>
|
||||
<string name="apps_no_match">Няма приложения, отговарящи на „%1$s“.</string>
|
||||
<string name="apps_add_widget">Добавяне на приспособление</string>
|
||||
|
||||
@@ -3599,6 +3599,19 @@
|
||||
<string name="integration_shortcuts_service">Tlačítko Domů pro DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Předává stisky tlačítka Domů do Steam Guide, když je aktivní relace Steamu a DroidDeck je výchozí aplikací domovské obrazovky. Nečte obsah obrazovky.</string>
|
||||
<string name="integration_brightness_needed">Zapněte Jas v Příprava → Integrace se Steamem</string>
|
||||
<string name="setup_gpu_bridge_off">Most Mali: vypnuto</string>
|
||||
<string name="setup_gpu_bridge_on">Most Mali: zapnuto</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s není Adreno. Experimentální most Vulkan je zapnutý: relace vykresluje přes vlastní ovladač telefonu. Počítejte s chybami; pošlete protokoly relace.</string>
|
||||
<string name="setup_bridge_safe">Bezpečný režim</string>
|
||||
<string name="setup_bridge_safe_on">Bezpečný režim: zapnuto</string>
|
||||
<string name="setup_bridge_safe_off">Bezpečný režim: vypnuto</string>
|
||||
<string name="setup_bridge_safe_on_detail">Nejvyšší kompatibilita. Doporučeno pro nové zařízení.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Rychlejší tam, kde tento GPU prošel kontrolami.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automaticky</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: vlastní z Protonu</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Použije starší DXVK, pokud je Vulkan tohoto GPU pro DXVK z Protonu příliš starý.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Každý Proton si ponechá vlastní DXVK, i když pro něj může být Vulkan tohoto GPU příliš starý.</string>
|
||||
<string name="apps_search_hint">Hledat aplikace</string>
|
||||
<string name="apps_no_match">Výrazu „%1$s“ neodpovídá žádná aplikace.</string>
|
||||
<string name="apps_add_widget">Přidat widget</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck Hjem-knap</string>
|
||||
<string name="integration_shortcuts_service_description">Sender tryk på Hjem-knappen til Steam Guide, mens en Steam-session er i fokus, og DroidDeck er standardstartskærmen. Læser ikke skærmindhold.</string>
|
||||
<string name="integration_brightness_needed">Slå Lysstyrke til under Opsætning → Steam-integration</string>
|
||||
<string name="setup_gpu_bridge_off">Mali-bro: fra</string>
|
||||
<string name="setup_gpu_bridge_on">Mali-bro: til</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s er ikke en Adreno. Den eksperimentelle Vulkan-bro er slået til: sessionen tegner gennem telefonens egen driver. Forvent fejl; send sessionslogfilerne.</string>
|
||||
<string name="setup_bridge_safe">Sikker tilstand</string>
|
||||
<string name="setup_bridge_safe_on">Sikker tilstand: til</string>
|
||||
<string name="setup_bridge_safe_off">Sikker tilstand: fra</string>
|
||||
<string name="setup_bridge_safe_on_detail">Mest kompatibel. Anbefales til en ny enhed.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Hurtigere, hvor denne GPU har bestået sine tjek.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatisk</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Protons egen</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Bruger en ældre DXVK, hvor denne GPU\'s Vulkan er for gammel til Protons.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Hver Proton beholder sin egen DXVK, også hvor denne GPU\'s Vulkan kan være for gammel til den.</string>
|
||||
<string name="apps_search_hint">Søg i apps</string>
|
||||
<string name="apps_no_match">Ingen apps matcher “%1$s”.</string>
|
||||
<string name="apps_add_widget">Tilføj widget</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck-Home-Taste</string>
|
||||
<string name="integration_shortcuts_service_description">Leitet Drücke der Home-Taste an Steam Guide weiter, während eine Steam-Sitzung im Fokus ist und DroidDeck die Standard-Startbildschirm-App ist. Liest keine Bildschirminhalte.</string>
|
||||
<string name="integration_brightness_needed">Aktiviere Helligkeit unter Einrichtung → Steam-Integration</string>
|
||||
<string name="setup_gpu_bridge_off">Mali-Brücke: aus</string>
|
||||
<string name="setup_gpu_bridge_on">Mali-Brücke: an</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s ist kein Adreno. Die experimentelle Vulkan-Brücke ist an: Die Sitzung zeichnet über den eigenen Treiber des Telefons. Rechne mit Fehlern und sende die Sitzungsprotokolle.</string>
|
||||
<string name="setup_bridge_safe">Sicherer Modus</string>
|
||||
<string name="setup_bridge_safe_on">Sicherer Modus: an</string>
|
||||
<string name="setup_bridge_safe_off">Sicherer Modus: aus</string>
|
||||
<string name="setup_bridge_safe_on_detail">Am kompatibelsten. Empfohlen für ein neues Gerät.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Schneller, wo diese GPU ihre Prüfungen bestanden hat.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatisch</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: von Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Nutzt ein älteres DXVK, wenn das Vulkan dieser GPU für das von Proton zu alt ist.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Jedes Proton behält sein eigenes DXVK, auch wenn das Vulkan dieser GPU dafür zu alt sein könnte.</string>
|
||||
<string name="apps_search_hint">Apps durchsuchen</string>
|
||||
<string name="apps_no_match">Keine Apps passen zu „%1$s“.</string>
|
||||
<string name="apps_add_widget">Widget hinzufügen</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">Κουμπί Αρχικής DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Μεταβιβάζει τα πατήματα του κουμπιού Αρχικής στο Steam Guide όσο μια συνεδρία Steam είναι εστιασμένη και το DroidDeck είναι η προεπιλεγμένη εφαρμογή Αρχικής. Δεν διαβάζει το περιεχόμενο της οθόνης.</string>
|
||||
<string name="integration_brightness_needed">Ενεργοποιήστε τη Φωτεινότητα στη Ρύθμιση → Ενσωμάτωση Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Γέφυρα Mali: ανενεργή</string>
|
||||
<string name="setup_gpu_bridge_on">Γέφυρα Mali: ενεργή</string>
|
||||
<string name="setup_gpu_bridge_detail">Η %1$s δεν είναι Adreno. Η πειραματική γέφυρα Vulkan είναι ενεργή: η συνεδρία σχεδιάζει μέσω του προγράμματος οδήγησης του ίδιου του τηλεφώνου. Αναμένετε σφάλματα· στείλτε τα αρχεία καταγραφής της συνεδρίας.</string>
|
||||
<string name="setup_bridge_safe">Ασφαλής λειτουργία</string>
|
||||
<string name="setup_bridge_safe_on">Ασφαλής λειτουργία: ενεργή</string>
|
||||
<string name="setup_bridge_safe_off">Ασφαλής λειτουργία: ανενεργή</string>
|
||||
<string name="setup_bridge_safe_on_detail">Μέγιστη συμβατότητα. Συνιστάται για νέα συσκευή.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Ταχύτερη όπου αυτή η GPU πέρασε τους ελέγχους της.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: αυτόματο</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: του Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Χρησιμοποιεί παλαιότερο DXVK όταν το Vulkan αυτής της GPU είναι πολύ παλιό για αυτό του Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Κάθε Proton κρατά το δικό του DXVK, ακόμη κι αν το Vulkan αυτής της GPU ίσως είναι πολύ παλιό για αυτό.</string>
|
||||
<string name="apps_search_hint">Αναζήτηση εφαρμογών</string>
|
||||
<string name="apps_no_match">Καμία εφαρμογή δεν ταιριάζει με «%1$s».</string>
|
||||
<string name="apps_add_widget">Προσθήκη γραφικού στοιχείου</string>
|
||||
|
||||
@@ -3563,6 +3563,19 @@
|
||||
<string name="integration_shortcuts_service">Botón de inicio de DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Pasa las pulsaciones del botón de inicio a Steam Guide mientras una sesión de Steam está en primer plano y DroidDeck es la app de inicio predeterminada. No lee el contenido de la pantalla.</string>
|
||||
<string name="integration_brightness_needed">Activa Brillo en Configuración → Integración con Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Puente Mali: desactivado</string>
|
||||
<string name="setup_gpu_bridge_on">Puente Mali: activado</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s no es una Adreno. El puente Vulkan experimental está activado: la sesión dibuja a través del propio controlador del teléfono. Espera errores; envía los registros de la sesión.</string>
|
||||
<string name="setup_bridge_safe">Modo seguro</string>
|
||||
<string name="setup_bridge_safe_on">Modo seguro: activado</string>
|
||||
<string name="setup_bridge_safe_off">Modo seguro: desactivado</string>
|
||||
<string name="setup_bridge_safe_on_detail">El más compatible. Recomendado para un dispositivo nuevo.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Más rápido donde esta GPU superó sus comprobaciones.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automático</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: el de Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Usa un DXVK más antiguo cuando el Vulkan de esta GPU es demasiado antiguo para el de Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Cada Proton conserva su propio DXVK, aunque el Vulkan de esta GPU pueda ser demasiado antiguo para él.</string>
|
||||
<string name="apps_search_hint">Buscar aplicaciones</string>
|
||||
<string name="apps_no_match">Ninguna aplicación coincide con «%1$s».</string>
|
||||
<string name="apps_add_widget">Añadir widget</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeckin Koti-painike</string>
|
||||
<string name="integration_shortcuts_service_description">Välittää Koti-painikkeen painallukset Steam Guidelle, kun Steam-istunto on aktiivinen ja DroidDeck on oletusaloitusnäyttösovellus. Ei lue näytön sisältöä.</string>
|
||||
<string name="integration_brightness_needed">Ota Kirkkaus käyttöön kohdassa Käyttöönotto → Steam-integraatio</string>
|
||||
<string name="setup_gpu_bridge_off">Mali-silta: pois</string>
|
||||
<string name="setup_gpu_bridge_on">Mali-silta: päällä</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s ei ole Adreno. Kokeellinen Vulkan-silta on päällä: istunto piirtää puhelimen oman ajurin kautta. Odota virheitä; lähetä istunnon lokit.</string>
|
||||
<string name="setup_bridge_safe">Turvatila</string>
|
||||
<string name="setup_bridge_safe_on">Turvatila: päällä</string>
|
||||
<string name="setup_bridge_safe_off">Turvatila: pois</string>
|
||||
<string name="setup_bridge_safe_on_detail">Yhteensopivin. Suositellaan uudelle laitteelle.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Nopeampi siellä, missä tämä GPU läpäisi tarkistuksensa.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automaattinen</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Protonin oma</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Käyttää vanhempaa DXVK:ta, kun tämän GPU:n Vulkan on liian vanha Protonin omalle.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Jokainen Proton pitää oman DXVK:nsa, vaikka tämän GPU:n Vulkan voi olla sille liian vanha.</string>
|
||||
<string name="apps_search_hint">Hae sovelluksia</string>
|
||||
<string name="apps_no_match">Mikään sovellus ei vastaa hakua ”%1$s”.</string>
|
||||
<string name="apps_add_widget">Lisää widget</string>
|
||||
|
||||
@@ -3563,6 +3563,19 @@
|
||||
<string name="integration_shortcuts_service">Bouton Accueil DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Transmet les appuis sur le bouton Accueil à Steam Guide lorsqu\'une session Steam est au premier plan et que DroidDeck est l\'application d\'écran d\'accueil par défaut. Ne lit pas le contenu de l\'écran.</string>
|
||||
<string name="integration_brightness_needed">Activez Luminosité dans Paramètres → Intégration Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Pont Mali : désactivé</string>
|
||||
<string name="setup_gpu_bridge_on">Pont Mali : activé</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s n\'est pas un Adreno. Le pont Vulkan expérimental est activé : la session dessine via le pilote du téléphone lui-même. Attendez-vous à des bugs ; envoyez les journaux de session.</string>
|
||||
<string name="setup_bridge_safe">Mode sans échec</string>
|
||||
<string name="setup_bridge_safe_on">Mode sans échec : activé</string>
|
||||
<string name="setup_bridge_safe_off">Mode sans échec : désactivé</string>
|
||||
<string name="setup_bridge_safe_on_detail">Le plus compatible. Recommandé pour un nouvel appareil.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Plus rapide là où ce GPU a réussi ses vérifications.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK : automatique</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK : celui de Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Utilise un DXVK plus ancien quand le Vulkan de ce GPU est trop ancien pour celui de Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Chaque Proton garde son propre DXVK, même quand le Vulkan de ce GPU risque d\'être trop ancien pour lui.</string>
|
||||
<string name="apps_search_hint">Rechercher des applications</string>
|
||||
<string name="apps_no_match">Aucune application ne correspond à « %1$s ».</string>
|
||||
<string name="apps_add_widget">Ajouter un widget</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck होम बटन</string>
|
||||
<string name="integration_shortcuts_service_description">जब Steam सेशन फ़ोकस में हो और DroidDeck डिफ़ॉल्ट होम ऐप हो, तब होम बटन दबाने को Steam Guide तक भेजता है। स्क्रीन की सामग्री नहीं पढ़ता।</string>
|
||||
<string name="integration_brightness_needed">Setup → Steam इंटीग्रेशन में ब्राइटनेस चालू करें</string>
|
||||
<string name="setup_gpu_bridge_off">Mali ब्रिज: बंद</string>
|
||||
<string name="setup_gpu_bridge_on">Mali ब्रिज: चालू</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s Adreno नहीं है। प्रायोगिक Vulkan ब्रिज चालू है: सत्र फ़ोन के अपने ड्राइवर से ड्रॉ करता है। बग की उम्मीद रखें; सत्र लॉग भेजें।</string>
|
||||
<string name="setup_bridge_safe">सुरक्षित मोड</string>
|
||||
<string name="setup_bridge_safe_on">सुरक्षित मोड: चालू</string>
|
||||
<string name="setup_bridge_safe_off">सुरक्षित मोड: बंद</string>
|
||||
<string name="setup_bridge_safe_on_detail">सबसे अधिक संगत। नए डिवाइस के लिए अनुशंसित।</string>
|
||||
<string name="setup_bridge_safe_off_detail">जहाँ इस GPU ने अपनी जाँचें पास कीं, वहाँ तेज़।</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: स्वचालित</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Proton का अपना</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">जहाँ इस GPU का Vulkan Proton के DXVK के लिए बहुत पुराना है, वहाँ पुराना DXVK इस्तेमाल करता है।</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">हर Proton अपना DXVK रखता है, भले ही इस GPU का Vulkan उसके लिए बहुत पुराना हो।</string>
|
||||
<string name="apps_search_hint">ऐप खोजें</string>
|
||||
<string name="apps_no_match">“%1$s” से कोई ऐप मेल नहीं खाता।</string>
|
||||
<string name="apps_add_widget">विजेट जोड़ें</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck Kezdőképernyő gomb</string>
|
||||
<string name="integration_shortcuts_service_description">A Kezdőképernyő gomb lenyomásait továbbítja a Steam Guide-nak, amíg egy Steam-munkamenet van fókuszban, és a DroidDeck az alapértelmezett kezdőképernyő alkalmazás. Nem olvassa a képernyő tartalmát.</string>
|
||||
<string name="integration_brightness_needed">Kapcsolja be a Fényerőt itt: Beállítás → Steam-integráció</string>
|
||||
<string name="setup_gpu_bridge_off">Mali híd: ki</string>
|
||||
<string name="setup_gpu_bridge_on">Mali híd: be</string>
|
||||
<string name="setup_gpu_bridge_detail">A(z) %1$s nem Adreno. A kísérleti Vulkan híd be van kapcsolva: a munkamenet a telefon saját illesztőprogramján keresztül rajzol. Hibákra számíts; küldd el a munkamenet naplóit.</string>
|
||||
<string name="setup_bridge_safe">Biztonságos mód</string>
|
||||
<string name="setup_bridge_safe_on">Biztonságos mód: be</string>
|
||||
<string name="setup_bridge_safe_off">Biztonságos mód: ki</string>
|
||||
<string name="setup_bridge_safe_on_detail">A legjobb kompatibilitás. Új eszközhöz ajánlott.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Gyorsabb ott, ahol ez a GPU átment az ellenőrzéseken.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatikus</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: a Protoné</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Régebbi DXVK-t használ, ha ennek a GPU-nak a Vulkanja túl régi a Proton sajátjához.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Minden Proton megtartja a saját DXVK-ját, akkor is, ha ennek a GPU-nak a Vulkanja túl régi lehet hozzá.</string>
|
||||
<string name="apps_search_hint">Alkalmazások keresése</string>
|
||||
<string name="apps_no_match">Egy alkalmazás sem felel meg ennek: „%1$s”.</string>
|
||||
<string name="apps_add_widget">Modul hozzáadása</string>
|
||||
|
||||
@@ -3500,6 +3500,19 @@
|
||||
<string name="integration_shortcuts_service">Tombol Home DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Meneruskan tekanan tombol Home ke Steam Guide saat sesi Steam sedang aktif dan DroidDeck adalah aplikasi Beranda default. Tidak membaca konten layar.</string>
|
||||
<string name="integration_brightness_needed">Aktifkan Kecerahan di Penyiapan → Integrasi Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Jembatan Mali: mati</string>
|
||||
<string name="setup_gpu_bridge_on">Jembatan Mali: nyala</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s bukan Adreno. Jembatan Vulkan eksperimental menyala: sesi menggambar melalui driver ponsel itu sendiri. Bersiaplah menemui bug; kirim log sesi.</string>
|
||||
<string name="setup_bridge_safe">Mode aman</string>
|
||||
<string name="setup_bridge_safe_on">Mode aman: nyala</string>
|
||||
<string name="setup_bridge_safe_off">Mode aman: mati</string>
|
||||
<string name="setup_bridge_safe_on_detail">Paling kompatibel. Disarankan untuk perangkat baru.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Lebih cepat di bagian yang lolos pemeriksaan GPU ini.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: otomatis</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: bawaan Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Memakai DXVK lebih lama jika Vulkan GPU ini terlalu lama untuk DXVK bawaan Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Setiap Proton tetap memakai DXVK bawaannya, meski Vulkan GPU ini mungkin terlalu lama untuknya.</string>
|
||||
<string name="apps_search_hint">Cari aplikasi</string>
|
||||
<string name="apps_no_match">Tidak ada aplikasi yang cocok dengan “%1$s”.</string>
|
||||
<string name="apps_add_widget">Tambahkan widget</string>
|
||||
|
||||
@@ -3563,6 +3563,19 @@
|
||||
<string name="integration_shortcuts_service">Tasto Home di DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Inoltra le pressioni del tasto Home a Steam Guide mentre una sessione Steam è in primo piano e DroidDeck è l\'app Home predefinita. Non legge il contenuto dello schermo.</string>
|
||||
<string name="integration_brightness_needed">Attiva Luminosità in Configurazione → Integrazione con Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Ponte Mali: disattivato</string>
|
||||
<string name="setup_gpu_bridge_on">Ponte Mali: attivo</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s non è una Adreno. Il ponte Vulkan sperimentale è attivo: la sessione disegna tramite il driver del telefono. Aspettati bug; invia i log della sessione.</string>
|
||||
<string name="setup_bridge_safe">Modalità sicura</string>
|
||||
<string name="setup_bridge_safe_on">Modalità sicura: attiva</string>
|
||||
<string name="setup_bridge_safe_off">Modalità sicura: disattivata</string>
|
||||
<string name="setup_bridge_safe_on_detail">La più compatibile. Consigliata per un nuovo dispositivo.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Più veloce dove questa GPU ha superato i controlli.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatico</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: quello di Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Usa un DXVK più vecchio se il Vulkan di questa GPU è troppo vecchio per quello di Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Ogni Proton mantiene il proprio DXVK, anche se il Vulkan di questa GPU potrebbe essere troppo vecchio.</string>
|
||||
<string name="apps_search_hint">Cerca app</string>
|
||||
<string name="apps_no_match">Nessuna app corrisponde a «%1$s».</string>
|
||||
<string name="apps_add_widget">Aggiungi widget</string>
|
||||
|
||||
@@ -3500,6 +3500,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeckホームボタン</string>
|
||||
<string name="integration_shortcuts_service_description">Steamセッションがフォーカスされ、DroidDeckがデフォルトのホームアプリの場合に、ホームボタンの操作をSteamガイドに渡します。画面のコンテンツは読み取りません。</string>
|
||||
<string name="integration_brightness_needed">「セットアップ」→「Steam連携」で明るさをオンにしてください</string>
|
||||
<string name="setup_gpu_bridge_off">Mali ブリッジ: オフ</string>
|
||||
<string name="setup_gpu_bridge_on">Mali ブリッジ: オン</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s は Adreno ではありません。試験的な Vulkan ブリッジがオンです。セッションはスマートフォン本体のドライバーで描画します。不具合が出る可能性があります。セッションログを送ってください。</string>
|
||||
<string name="setup_bridge_safe">セーフモード</string>
|
||||
<string name="setup_bridge_safe_on">セーフモード: オン</string>
|
||||
<string name="setup_bridge_safe_off">セーフモード: オフ</string>
|
||||
<string name="setup_bridge_safe_on_detail">最も互換性が高い設定です。新しいデバイスにおすすめです。</string>
|
||||
<string name="setup_bridge_safe_off_detail">この GPU がチェックに合格した部分で高速になります。</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: 自動</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Proton 付属</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">この GPU の Vulkan が Proton 付属の DXVK には古すぎる場合、古い DXVK を使います。</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">この GPU の Vulkan が古すぎる場合でも、各 Proton は付属の DXVK を使い続けます。</string>
|
||||
<string name="apps_search_hint">アプリを検索</string>
|
||||
<string name="apps_no_match">「%1$s」に一致するアプリはありません。</string>
|
||||
<string name="apps_add_widget">ウィジェットを追加</string>
|
||||
|
||||
@@ -3500,6 +3500,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck 홈 버튼</string>
|
||||
<string name="integration_shortcuts_service_description">Steam 세션에 포커스가 있고 DroidDeck이 기본 홈 앱일 때 홈 버튼 입력을 Steam Guide로 전달합니다. 화면 콘텐츠는 읽지 않습니다.</string>
|
||||
<string name="integration_brightness_needed">설정 → Steam 연동에서 밝기를 켜세요</string>
|
||||
<string name="setup_gpu_bridge_off">Mali 브리지: 끔</string>
|
||||
<string name="setup_gpu_bridge_on">Mali 브리지: 켬</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s은(는) Adreno가 아닙니다. 실험적 Vulkan 브리지가 켜져 있어 세션이 휴대폰 자체 드라이버로 그립니다. 버그가 있을 수 있으니 세션 로그를 보내 주세요.</string>
|
||||
<string name="setup_bridge_safe">안전 모드</string>
|
||||
<string name="setup_bridge_safe_on">안전 모드: 켬</string>
|
||||
<string name="setup_bridge_safe_off">안전 모드: 끔</string>
|
||||
<string name="setup_bridge_safe_on_detail">호환성이 가장 높습니다. 새 기기에 권장합니다.</string>
|
||||
<string name="setup_bridge_safe_off_detail">이 GPU가 검사를 통과한 부분에서 더 빠릅니다.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: 자동</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Proton 기본</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">이 GPU의 Vulkan이 Proton 기본 DXVK에 너무 오래된 경우 이전 DXVK를 사용합니다.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">이 GPU의 Vulkan이 너무 오래되었을 수 있어도 각 Proton은 자체 DXVK를 유지합니다.</string>
|
||||
<string name="apps_search_hint">앱 검색</string>
|
||||
<string name="apps_no_match">‘%1$s’와(과) 일치하는 앱이 없습니다.</string>
|
||||
<string name="apps_add_widget">위젯 추가</string>
|
||||
|
||||
@@ -3500,6 +3500,19 @@
|
||||
<string name="integration_shortcuts_service">Butang Laman Utama DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Menghantar tekanan butang Laman Utama kepada Steam Guide semasa sesi Steam difokuskan dan DroidDeck ialah apl Laman Utama lalai. Tidak membaca kandungan skrin.</string>
|
||||
<string name="integration_brightness_needed">Hidupkan Kecerahan dalam Persediaan → Integrasi Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Jambatan Mali: mati</string>
|
||||
<string name="setup_gpu_bridge_on">Jambatan Mali: hidup</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s bukan Adreno. Jambatan Vulkan eksperimen dihidupkan: sesi melukis melalui pemacu telefon itu sendiri. Jangkakan pepijat; hantar log sesi.</string>
|
||||
<string name="setup_bridge_safe">Mod selamat</string>
|
||||
<string name="setup_bridge_safe_on">Mod selamat: hidup</string>
|
||||
<string name="setup_bridge_safe_off">Mod selamat: mati</string>
|
||||
<string name="setup_bridge_safe_on_detail">Paling serasi. Disyorkan untuk peranti baharu.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Lebih pantas di bahagian yang lulus semakan GPU ini.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatik</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: milik Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Menggunakan DXVK yang lebih lama jika Vulkan GPU ini terlalu lama untuk DXVK Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Setiap Proton mengekalkan DXVK sendiri, walaupun Vulkan GPU ini mungkin terlalu lama untuknya.</string>
|
||||
<string name="apps_search_hint">Cari apl</string>
|
||||
<string name="apps_no_match">Tiada apl sepadan dengan “%1$s”.</string>
|
||||
<string name="apps_add_widget">Tambah widget</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck Hjem-knapp</string>
|
||||
<string name="integration_shortcuts_service_description">Sender trykk på Hjem-knappen til Steam Guide mens en Steam-sesjon er i fokus og DroidDeck er standard startskjerm-app. Leser ikke skjerminnhold.</string>
|
||||
<string name="integration_brightness_needed">Slå på Lysstyrke i Oppsett → Steam-integrering</string>
|
||||
<string name="setup_gpu_bridge_off">Mali-bro: av</string>
|
||||
<string name="setup_gpu_bridge_on">Mali-bro: på</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s er ikke en Adreno. Den eksperimentelle Vulkan-broen er på: økten tegner gjennom telefonens egen driver. Forvent feil; send øktloggene.</string>
|
||||
<string name="setup_bridge_safe">Sikker modus</string>
|
||||
<string name="setup_bridge_safe_on">Sikker modus: på</string>
|
||||
<string name="setup_bridge_safe_off">Sikker modus: av</string>
|
||||
<string name="setup_bridge_safe_on_detail">Mest kompatibel. Anbefales for en ny enhet.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Raskere der denne GPU-en besto kontrollene sine.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatisk</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Protons egen</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Bruker en eldre DXVK der denne GPU-ens Vulkan er for gammel for Protons.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Hver Proton beholder sin egen DXVK, selv der denne GPU-ens Vulkan kan være for gammel for den.</string>
|
||||
<string name="apps_search_hint">Søk i apper</string>
|
||||
<string name="apps_no_match">Ingen apper samsvarer med «%1$s».</string>
|
||||
<string name="apps_add_widget">Legg til modul</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck-startknop</string>
|
||||
<string name="integration_shortcuts_service_description">Geeft drukken op de startknop door aan Steam Guide terwijl een Steam-sessie actief is en DroidDeck de standaard startscherm-app is. Leest geen scherminhoud.</string>
|
||||
<string name="integration_brightness_needed">Zet Helderheid aan in Installatie → Steam-integratie</string>
|
||||
<string name="setup_gpu_bridge_off">Mali-brug: uit</string>
|
||||
<string name="setup_gpu_bridge_on">Mali-brug: aan</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s is geen Adreno. De experimentele Vulkan-brug staat aan: de sessie tekent via het eigen stuurprogramma van de telefoon. Verwacht fouten; stuur de sessielogboeken.</string>
|
||||
<string name="setup_bridge_safe">Veilige modus</string>
|
||||
<string name="setup_bridge_safe_on">Veilige modus: aan</string>
|
||||
<string name="setup_bridge_safe_off">Veilige modus: uit</string>
|
||||
<string name="setup_bridge_safe_on_detail">Meest compatibel. Aanbevolen voor een nieuw apparaat.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Sneller waar deze GPU zijn controles heeft doorstaan.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatisch</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: die van Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Gebruikt een oudere DXVK als de Vulkan van deze GPU te oud is voor die van Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Elke Proton houdt zijn eigen DXVK, ook als de Vulkan van deze GPU daarvoor te oud kan zijn.</string>
|
||||
<string name="apps_search_hint">Apps zoeken</string>
|
||||
<string name="apps_no_match">Geen apps gevonden voor ‘%1$s’.</string>
|
||||
<string name="apps_add_widget">Widget toevoegen</string>
|
||||
|
||||
@@ -3599,6 +3599,19 @@
|
||||
<string name="integration_shortcuts_service">Przycisk ekranu głównego DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Przekazuje naciśnięcia przycisku ekranu głównego do Steam Guide, gdy aktywna jest sesja Steam, a DroidDeck jest domyślną aplikacją ekranu głównego. Nie odczytuje zawartości ekranu.</string>
|
||||
<string name="integration_brightness_needed">Włącz Jasność w Konfiguracja → Integracja ze Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Most Mali: wył.</string>
|
||||
<string name="setup_gpu_bridge_on">Most Mali: wł.</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s to nie Adreno. Eksperymentalny most Vulkan jest włączony: sesja rysuje przez własny sterownik telefonu. Spodziewaj się błędów; wyślij logi sesji.</string>
|
||||
<string name="setup_bridge_safe">Tryb bezpieczny</string>
|
||||
<string name="setup_bridge_safe_on">Tryb bezpieczny: wł.</string>
|
||||
<string name="setup_bridge_safe_off">Tryb bezpieczny: wył.</string>
|
||||
<string name="setup_bridge_safe_on_detail">Największa zgodność. Zalecany dla nowego urządzenia.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Szybciej tam, gdzie ten GPU przeszedł testy.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatycznie</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: z Protona</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Używa starszego DXVK, gdy Vulkan tego GPU jest za stary dla DXVK z Protona.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Każdy Proton zachowuje własny DXVK, nawet jeśli Vulkan tego GPU może być dla niego za stary.</string>
|
||||
<string name="apps_search_hint">Szukaj aplikacji</string>
|
||||
<string name="apps_no_match">Żadna aplikacja nie pasuje do „%1$s”.</string>
|
||||
<string name="apps_add_widget">Dodaj widżet</string>
|
||||
|
||||
@@ -3563,6 +3563,19 @@
|
||||
<string name="integration_shortcuts_service">Botão Página inicial do DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Envia os toques no botão Página inicial para o Guide do Steam enquanto uma sessão do Steam está em foco e o DroidDeck é a app de página inicial predefinida. Não lê o conteúdo do ecrã.</string>
|
||||
<string name="integration_brightness_needed">Ative Brilho em Configuração → Integração com o Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Ponte Mali: desligada</string>
|
||||
<string name="setup_gpu_bridge_on">Ponte Mali: ligada</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s não é uma Adreno. A ponte Vulkan experimental está ligada: a sessão desenha através do próprio controlador do telemóvel. Conte com erros; envie os registos da sessão.</string>
|
||||
<string name="setup_bridge_safe">Modo seguro</string>
|
||||
<string name="setup_bridge_safe_on">Modo seguro: ligado</string>
|
||||
<string name="setup_bridge_safe_off">Modo seguro: desligado</string>
|
||||
<string name="setup_bridge_safe_on_detail">O mais compatível. Recomendado para um dispositivo novo.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Mais rápido onde esta GPU passou nas verificações.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automático</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: o do Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Usa um DXVK mais antigo quando o Vulkan desta GPU é demasiado antigo para o do Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Cada Proton mantém o seu próprio DXVK, mesmo quando o Vulkan desta GPU pode ser demasiado antigo para ele.</string>
|
||||
<string name="apps_search_hint">Pesquisar aplicações</string>
|
||||
<string name="apps_no_match">Nenhuma aplicação corresponde a “%1$s”.</string>
|
||||
<string name="apps_add_widget">Adicionar widget</string>
|
||||
|
||||
@@ -3563,6 +3563,19 @@
|
||||
<string name="integration_shortcuts_service">Botão Início do DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Repassa os toques no botão Início para o Guide da Steam enquanto uma sessão da Steam está em foco e o DroidDeck é o app de tela inicial padrão. Não lê o conteúdo da tela.</string>
|
||||
<string name="integration_brightness_needed">Ative Brilho em Configuração → Integração com a Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Ponte Mali: desligada</string>
|
||||
<string name="setup_gpu_bridge_on">Ponte Mali: ligada</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s não é uma Adreno. A ponte Vulkan experimental está ligada: a sessão desenha pelo próprio driver do celular. Espere bugs; envie os logs da sessão.</string>
|
||||
<string name="setup_bridge_safe">Modo seguro</string>
|
||||
<string name="setup_bridge_safe_on">Modo seguro: ligado</string>
|
||||
<string name="setup_bridge_safe_off">Modo seguro: desligado</string>
|
||||
<string name="setup_bridge_safe_on_detail">O mais compatível. Recomendado para um dispositivo novo.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Mais rápido onde esta GPU passou nas verificações.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automático</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: o do Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Usa um DXVK mais antigo quando o Vulkan desta GPU é antigo demais para o do Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Cada Proton mantém o próprio DXVK, mesmo quando o Vulkan desta GPU pode ser antigo demais para ele.</string>
|
||||
<string name="apps_search_hint">Pesquisar apps</string>
|
||||
<string name="apps_no_match">Nenhum app corresponde a “%1$s”.</string>
|
||||
<string name="apps_add_widget">Adicionar widget</string>
|
||||
|
||||
@@ -3563,6 +3563,19 @@
|
||||
<string name="integration_shortcuts_service">Butonul Acasă DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Transmite apăsările butonului Acasă către Steam Guide cât timp o sesiune Steam este activă și DroidDeck este aplicația Acasă implicită. Nu citește conținutul ecranului.</string>
|
||||
<string name="integration_brightness_needed">Activează Luminozitate în Configurare → Integrare Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Punte Mali: oprită</string>
|
||||
<string name="setup_gpu_bridge_on">Punte Mali: pornită</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s nu este Adreno. Puntea Vulkan experimentală este pornită: sesiunea desenează prin driverul propriu al telefonului. Așteaptă-te la erori; trimite jurnalele sesiunii.</string>
|
||||
<string name="setup_bridge_safe">Mod sigur</string>
|
||||
<string name="setup_bridge_safe_on">Mod sigur: pornit</string>
|
||||
<string name="setup_bridge_safe_off">Mod sigur: oprit</string>
|
||||
<string name="setup_bridge_safe_on_detail">Cel mai compatibil. Recomandat pentru un dispozitiv nou.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Mai rapid acolo unde acest GPU a trecut verificările.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automat</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: al Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Folosește un DXVK mai vechi când Vulkan-ul acestui GPU este prea vechi pentru cel din Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Fiecare Proton își păstrează propriul DXVK, chiar dacă Vulkan-ul acestui GPU poate fi prea vechi pentru el.</string>
|
||||
<string name="apps_search_hint">Caută aplicații</string>
|
||||
<string name="apps_no_match">Nicio aplicație nu se potrivește cu „%1$s”.</string>
|
||||
<string name="apps_add_widget">Adaugă widget</string>
|
||||
|
||||
@@ -3599,6 +3599,19 @@
|
||||
<string name="integration_shortcuts_service">Кнопка «Домой» DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Передаёт нажатия кнопки «Домой» в Steam Guide, пока активна сессия Steam и DroidDeck является главным экраном по умолчанию. Не считывает содержимое экрана.</string>
|
||||
<string name="integration_brightness_needed">Включите «Яркость» в «Настройка → Интеграция со Steam»</string>
|
||||
<string name="setup_gpu_bridge_off">Мост Mali: выкл.</string>
|
||||
<string name="setup_gpu_bridge_on">Мост Mali: вкл.</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s — не Adreno. Включён экспериментальный мост Vulkan: сеанс рисует через собственный драйвер телефона. Возможны ошибки; отправьте журналы сеанса.</string>
|
||||
<string name="setup_bridge_safe">Безопасный режим</string>
|
||||
<string name="setup_bridge_safe_on">Безопасный режим: вкл.</string>
|
||||
<string name="setup_bridge_safe_off">Безопасный режим: выкл.</string>
|
||||
<string name="setup_bridge_safe_on_detail">Максимальная совместимость. Рекомендуется для нового устройства.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Быстрее там, где этот GPU прошёл проверки.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: автоматически</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: из Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Использует более старый DXVK, если Vulkan этого GPU слишком стар для DXVK из Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Каждый Proton оставляет свой DXVK, даже если Vulkan этого GPU может быть для него слишком старым.</string>
|
||||
<string name="apps_search_hint">Поиск приложений</string>
|
||||
<string name="apps_no_match">Нет приложений по запросу «%1$s».</string>
|
||||
<string name="apps_add_widget">Добавить виджет</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck hemknapp</string>
|
||||
<string name="integration_shortcuts_service_description">Skickar tryck på hemknappen till Steam Guide medan en Steam-session är i fokus och DroidDeck är standardappen för startskärmen. Läser inte skärminnehåll.</string>
|
||||
<string name="integration_brightness_needed">Slå på Ljusstyrka under Konfiguration → Steam-integration</string>
|
||||
<string name="setup_gpu_bridge_off">Mali-brygga: av</string>
|
||||
<string name="setup_gpu_bridge_on">Mali-brygga: på</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s är inte en Adreno. Den experimentella Vulkan-bryggan är på: sessionen ritar via telefonens egen drivrutin. Räkna med buggar; skicka sessionsloggarna.</string>
|
||||
<string name="setup_bridge_safe">Säkert läge</string>
|
||||
<string name="setup_bridge_safe_on">Säkert läge: på</string>
|
||||
<string name="setup_bridge_safe_off">Säkert läge: av</string>
|
||||
<string name="setup_bridge_safe_on_detail">Mest kompatibelt. Rekommenderas för en ny enhet.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Snabbare där den här GPU:n klarade sina kontroller.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatiskt</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Protons egen</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Använder en äldre DXVK när den här GPU:ns Vulkan är för gammal för Protons.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Varje Proton behåller sin egen DXVK, även där den här GPU:ns Vulkan kan vara för gammal för den.</string>
|
||||
<string name="apps_search_hint">Sök appar</string>
|
||||
<string name="apps_no_match">Inga appar matchar ”%1$s”.</string>
|
||||
<string name="apps_add_widget">Lägg till widget</string>
|
||||
|
||||
@@ -3500,6 +3500,19 @@
|
||||
<string name="integration_shortcuts_service">ปุ่มหน้าหลัก DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">ส่งการกดปุ่มหน้าหลักไปยัง Steam Guide ขณะเซสชัน Steam อยู่ในโฟกัสและ DroidDeck เป็นแอปหน้าหลักเริ่มต้น ไม่อ่านเนื้อหาบนหน้าจอ</string>
|
||||
<string name="integration_brightness_needed">เปิดความสว่างในการตั้งค่า → การผสานรวม Steam</string>
|
||||
<string name="setup_gpu_bridge_off">บริดจ์ Mali: ปิด</string>
|
||||
<string name="setup_gpu_bridge_on">บริดจ์ Mali: เปิด</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s ไม่ใช่ Adreno บริดจ์ Vulkan รุ่นทดลองเปิดอยู่: เซสชันวาดผ่านไดรเวอร์ของโทรศัพท์เอง อาจพบบั๊ก โปรดส่งบันทึกของเซสชัน</string>
|
||||
<string name="setup_bridge_safe">โหมดปลอดภัย</string>
|
||||
<string name="setup_bridge_safe_on">โหมดปลอดภัย: เปิด</string>
|
||||
<string name="setup_bridge_safe_off">โหมดปลอดภัย: ปิด</string>
|
||||
<string name="setup_bridge_safe_on_detail">เข้ากันได้มากที่สุด แนะนำสำหรับอุปกรณ์ใหม่</string>
|
||||
<string name="setup_bridge_safe_off_detail">เร็วขึ้นในส่วนที่ GPU นี้ผ่านการตรวจสอบ</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: อัตโนมัติ</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: ของ Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">ใช้ DXVK รุ่นเก่ากว่าเมื่อ Vulkan ของ GPU นี้เก่าเกินไปสำหรับ DXVK ของ Proton</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Proton แต่ละตัวใช้ DXVK ของตัวเอง แม้ Vulkan ของ GPU นี้อาจเก่าเกินไปสำหรับมัน</string>
|
||||
<string name="apps_search_hint">ค้นหาแอป</string>
|
||||
<string name="apps_no_match">ไม่มีแอปที่ตรงกับ “%1$s”</string>
|
||||
<string name="apps_add_widget">เพิ่มวิดเจ็ต</string>
|
||||
|
||||
@@ -3527,6 +3527,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck Ana ekran düğmesi</string>
|
||||
<string name="integration_shortcuts_service_description">Bir Steam oturumu odaktayken ve DroidDeck varsayılan Ana ekran uygulamasıyken Ana ekran düğmesine basışları Steam Guide\'a iletir. Ekran içeriğini okumaz.</string>
|
||||
<string name="integration_brightness_needed">Kurulum → Steam entegrasyonu bölümünde Parlaklık\'ı açın</string>
|
||||
<string name="setup_gpu_bridge_off">Mali köprüsü: kapalı</string>
|
||||
<string name="setup_gpu_bridge_on">Mali köprüsü: açık</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s bir Adreno değil. Deneysel Vulkan köprüsü açık: oturum, telefonun kendi sürücüsü üzerinden çizer. Hatalar olabilir; oturum günlüklerini gönderin.</string>
|
||||
<string name="setup_bridge_safe">Güvenli mod</string>
|
||||
<string name="setup_bridge_safe_on">Güvenli mod: açık</string>
|
||||
<string name="setup_bridge_safe_off">Güvenli mod: kapalı</string>
|
||||
<string name="setup_bridge_safe_on_detail">En uyumlu. Yeni bir cihaz için önerilir.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Bu GPU\'nun kontrollerini geçtiği yerlerde daha hızlı.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: otomatik</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Proton\'unki</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Bu GPU\'nun Vulkan\'ı Proton\'un DXVK\'sı için çok eskiyse daha eski bir DXVK kullanır.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Bu GPU\'nun Vulkan\'ı onun için çok eski olsa bile her Proton kendi DXVK\'sını korur.</string>
|
||||
<string name="apps_search_hint">Uygulama ara</string>
|
||||
<string name="apps_no_match">“%1$s” ile eşleşen uygulama yok.</string>
|
||||
<string name="apps_add_widget">Widget ekle</string>
|
||||
|
||||
@@ -3599,6 +3599,19 @@
|
||||
<string name="integration_shortcuts_service">Кнопка «Додому» DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Передає натискання кнопки «Додому» в Steam Guide, поки активний сеанс Steam і DroidDeck є типовим головним екраном. Не зчитує вміст екрана.</string>
|
||||
<string name="integration_brightness_needed">Увімкніть «Яскравість» у «Налаштування → Інтеграція зі Steam»</string>
|
||||
<string name="setup_gpu_bridge_off">Міст Mali: вимк.</string>
|
||||
<string name="setup_gpu_bridge_on">Міст Mali: увімк.</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s — не Adreno. Увімкнено експериментальний міст Vulkan: сеанс малює через власний драйвер телефона. Можливі помилки; надішліть журнали сеансу.</string>
|
||||
<string name="setup_bridge_safe">Безпечний режим</string>
|
||||
<string name="setup_bridge_safe_on">Безпечний режим: увімк.</string>
|
||||
<string name="setup_bridge_safe_off">Безпечний режим: вимк.</string>
|
||||
<string name="setup_bridge_safe_on_detail">Найкраща сумісність. Рекомендовано для нового пристрою.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Швидше там, де цей GPU пройшов перевірки.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: автоматично</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: з Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Використовує старіший DXVK, якщо Vulkan цього GPU застарий для DXVK з Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Кожен Proton залишає свій DXVK, навіть якщо Vulkan цього GPU може бути для нього застарим.</string>
|
||||
<string name="apps_search_hint">Пошук застосунків</string>
|
||||
<string name="apps_no_match">Немає застосунків за запитом «%1$s».</string>
|
||||
<string name="apps_add_widget">Додати віджет</string>
|
||||
|
||||
@@ -3500,6 +3500,19 @@
|
||||
<string name="integration_shortcuts_service">Nút Home DroidDeck</string>
|
||||
<string name="integration_shortcuts_service_description">Chuyển các lần nhấn nút Home đến Steam Guide khi phiên Steam đang được chọn và DroidDeck là ứng dụng màn hình chính mặc định. Không đọc nội dung màn hình.</string>
|
||||
<string name="integration_brightness_needed">Bật Độ sáng trong Thiết lập → Tích hợp Steam</string>
|
||||
<string name="setup_gpu_bridge_off">Cầu Mali: tắt</string>
|
||||
<string name="setup_gpu_bridge_on">Cầu Mali: bật</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s không phải Adreno. Cầu Vulkan thử nghiệm đang bật: phiên vẽ qua trình điều khiển của chính điện thoại. Có thể gặp lỗi; hãy gửi nhật ký phiên.</string>
|
||||
<string name="setup_bridge_safe">Chế độ an toàn</string>
|
||||
<string name="setup_bridge_safe_on">Chế độ an toàn: bật</string>
|
||||
<string name="setup_bridge_safe_off">Chế độ an toàn: tắt</string>
|
||||
<string name="setup_bridge_safe_on_detail">Tương thích nhất. Nên dùng cho thiết bị mới.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Nhanh hơn ở những chỗ GPU này đã vượt qua kiểm tra.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: tự động</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: của Proton</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Dùng DXVK cũ hơn khi Vulkan của GPU này quá cũ cho DXVK của Proton.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Mỗi Proton giữ DXVK riêng, kể cả khi Vulkan của GPU này có thể quá cũ cho nó.</string>
|
||||
<string name="apps_search_hint">Tìm ứng dụng</string>
|
||||
<string name="apps_no_match">Không có ứng dụng nào khớp với “%1$s”.</string>
|
||||
<string name="apps_add_widget">Thêm tiện ích</string>
|
||||
|
||||
@@ -802,6 +802,7 @@
|
||||
<string name="setup_gpu_supported">设备受支持</string>
|
||||
<string name="setup_gpu_untested">未经测试的 GPU</string>
|
||||
<string name="setup_gpu_unsupported">GPU 不受支持</string>
|
||||
<string name="setup_gpu_bridged">GPU 通过 Mali 桥接</string>
|
||||
<string name="setup_gpu_detail">%1$s · %2$s</string>
|
||||
<string name="setup_gpu_untested_detail">%1$s:%2$s</string>
|
||||
<string name="setup_gpu_unsupported_detail">Steam 需要 Adreno 驱动;%1$s 可能显示黑屏</string>
|
||||
@@ -2145,6 +2146,8 @@
|
||||
<string name="gpuinfo_a850_experimental">Adreno 850 实验支持:需要配套的实验版显示驱动和游戏驱动;尚未在此设备上验证</string>
|
||||
<string name="gpuinfo_below_tested">低于已测试的硬件(Adreno 725 及更新):可能无法运行</string>
|
||||
<string name="gpuinfo_unsupported">不受支持:DroidDeck 需要 Adreno(骁龙)GPU</string>
|
||||
<string name="gpuinfo_bridge">通过 Mali 桥接运行,使用手机自带的 Vulkan 驱动</string>
|
||||
<string name="gpuinfo_bridge_off">关闭 Mali 桥接时不受支持:Turnip 需要 Adreno(骁龙)GPU</string>
|
||||
|
||||
<string name="turnip_rate_limited">已达到 GitHub 的请求频率限制 — 请稍后重试</string>
|
||||
<string name="turnip_github_http">GitHub 返回 HTTP %1$d</string>
|
||||
@@ -3500,6 +3503,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck 主屏幕按钮</string>
|
||||
<string name="integration_shortcuts_service_description">当 Steam 会话处于焦点且 DroidDeck 为默认主屏幕应用时,将主屏幕按钮的按下操作传递给 Steam Guide 按钮。不会读取屏幕内容。</string>
|
||||
<string name="integration_brightness_needed">请在“配置”→“Steam 集成”中开启亮度</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK:自动</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK:Proton 自带</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">此 GPU 的 Vulkan 版本对 Proton 自带的 DXVK 来说太旧时,改用较旧的 DXVK。</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">每个 Proton 都使用自带的 DXVK,即使此 GPU 的 Vulkan 版本可能太旧。</string>
|
||||
<string name="setup_gpu_bridge_off">Mali 桥接:关</string>
|
||||
<string name="setup_gpu_bridge_on">Mali 桥接:开</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s 不是 Adreno。实验性 Vulkan 桥接已开启:会话通过手机自带的驱动绘制。可能会有问题,请发送会话日志。</string>
|
||||
<string name="setup_bridge_safe">安全模式</string>
|
||||
<string name="setup_bridge_safe_on">安全模式:开</string>
|
||||
<string name="setup_bridge_safe_off">安全模式:关</string>
|
||||
<string name="setup_bridge_safe_on_detail">兼容性最好。推荐新设备使用。</string>
|
||||
<string name="setup_bridge_safe_off_detail">在此 GPU 通过检查的地方更快。</string>
|
||||
<string name="apps_search_hint">搜索应用</string>
|
||||
<string name="apps_no_match">没有与“%1$s”匹配的应用。</string>
|
||||
<string name="apps_add_widget">添加微件</string>
|
||||
|
||||
@@ -3500,6 +3500,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck 主畫面按鈕</string>
|
||||
<string name="integration_shortcuts_service_description">當 Steam 會話處於焦點且 DroidDeck 為預設主畫面應用程式時,將主畫面按鈕的按壓傳送給 Steam Guide。不會讀取螢幕內容。</string>
|
||||
<string name="integration_brightness_needed">請在「初始設定 → Steam 整合」中開啟「亮度」</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK:自動</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK:Proton 內建</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">此 GPU 的 Vulkan 版本對 Proton 內建的 DXVK 來說太舊時,改用較舊的 DXVK。</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">每個 Proton 都使用內建的 DXVK,即使此 GPU 的 Vulkan 版本可能太舊。</string>
|
||||
<string name="setup_gpu_bridge_off">Mali 橋接:關</string>
|
||||
<string name="setup_gpu_bridge_on">Mali 橋接:開</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s 不是 Adreno。實驗性 Vulkan 橋接已開啟:工作階段透過手機內建的驅動程式繪製。可能會有問題,請傳送工作階段記錄。</string>
|
||||
<string name="setup_bridge_safe">安全模式</string>
|
||||
<string name="setup_bridge_safe_on">安全模式:開</string>
|
||||
<string name="setup_bridge_safe_off">安全模式:關</string>
|
||||
<string name="setup_bridge_safe_on_detail">相容性最好。建議新裝置使用。</string>
|
||||
<string name="setup_bridge_safe_off_detail">在此 GPU 通過檢查的地方較快。</string>
|
||||
<string name="apps_search_hint">搜尋應用程式</string>
|
||||
<string name="apps_no_match">沒有符合「%1$s」的應用程式。</string>
|
||||
<string name="apps_add_widget">新增小工具</string>
|
||||
@@ -3607,4 +3620,7 @@
|
||||
<string name="stores_gamejolt_filter_status">狀態</string>
|
||||
<string name="stores_gamejolt_filter_partners">合作夥伴</string>
|
||||
<string name="stores_gamejolt_followed">已追蹤的遊戲</string>
|
||||
<string name="setup_gpu_bridged">GPU 透過 Mali 橋接</string>
|
||||
<string name="gpuinfo_bridge">透過 Mali 橋接執行,使用手機內建的 Vulkan 驅動程式</string>
|
||||
<string name="gpuinfo_bridge_off">關閉 Mali 橋接時不受支援:Turnip 需要 Adreno(Snapdragon)GPU</string>
|
||||
</resources>
|
||||
@@ -3500,6 +3500,19 @@
|
||||
<string name="integration_shortcuts_service">DroidDeck 主畫面按鈕</string>
|
||||
<string name="integration_shortcuts_service_description">當 Steam 工作階段處於焦點且 DroidDeck 為預設主畫面應用程式時,將主畫面按鈕的按壓傳送給 Steam Guide。不會讀取螢幕內容。</string>
|
||||
<string name="integration_brightness_needed">請在「初始設定 → Steam 整合」中開啟「亮度」</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK:自動</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK:Proton 內建</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">此 GPU 的 Vulkan 版本對 Proton 內建的 DXVK 來說太舊時,改用較舊的 DXVK。</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">每個 Proton 都使用內建的 DXVK,即使此 GPU 的 Vulkan 版本可能太舊。</string>
|
||||
<string name="setup_gpu_bridge_off">Mali 橋接:關</string>
|
||||
<string name="setup_gpu_bridge_on">Mali 橋接:開</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s 不是 Adreno。實驗性 Vulkan 橋接已開啟:工作階段透過手機內建的驅動程式繪製。可能會有問題,請傳送工作階段記錄。</string>
|
||||
<string name="setup_bridge_safe">安全模式</string>
|
||||
<string name="setup_bridge_safe_on">安全模式:開</string>
|
||||
<string name="setup_bridge_safe_off">安全模式:關</string>
|
||||
<string name="setup_bridge_safe_on_detail">相容性最好。建議新裝置使用。</string>
|
||||
<string name="setup_bridge_safe_off_detail">在此 GPU 通過檢查的地方較快。</string>
|
||||
<string name="apps_search_hint">搜尋應用程式</string>
|
||||
<string name="apps_no_match">沒有符合「%1$s」的應用程式。</string>
|
||||
<string name="apps_add_widget">新增小工具</string>
|
||||
@@ -3607,4 +3620,7 @@
|
||||
<string name="stores_gamejolt_filter_status">狀態</string>
|
||||
<string name="stores_gamejolt_filter_partners">合作夥伴</string>
|
||||
<string name="stores_gamejolt_followed">已追蹤的遊戲</string>
|
||||
<string name="setup_gpu_bridged">GPU 透過 Mali 橋接</string>
|
||||
<string name="gpuinfo_bridge">透過 Mali 橋接執行,使用手機內建的 Vulkan 驅動程式</string>
|
||||
<string name="gpuinfo_bridge_off">關閉 Mali 橋接時不受支援:Turnip 需要 Adreno(Snapdragon)GPU</string>
|
||||
</resources>
|
||||
@@ -872,6 +872,7 @@
|
||||
<string name="setup_gpu_supported">Device supported</string>
|
||||
<string name="setup_gpu_untested">Untested GPU</string>
|
||||
<string name="setup_gpu_unsupported">GPU not supported</string>
|
||||
<string name="setup_gpu_bridged">GPU via Mali bridge</string>
|
||||
<string name="setup_gpu_detail">%1$s · %2$s</string>
|
||||
<string name="setup_gpu_untested_detail">%1$s: %2$s</string>
|
||||
<string name="setup_gpu_unsupported_detail">Steam needs an Adreno driver; %1$s may show a black screen</string>
|
||||
@@ -2250,6 +2251,8 @@
|
||||
<string name="gpuinfo_a850_experimental">Experimental Adreno 850 support: requires compatible experimental display and game drivers; not verified on this device</string>
|
||||
<string name="gpuinfo_below_tested">Below tested hardware (Adreno 725 and newer): it may not run</string>
|
||||
<string name="gpuinfo_unsupported">Not supported: DroidDeck needs an Adreno (Snapdragon) GPU</string>
|
||||
<string name="gpuinfo_bridge">Runs through the Mali bridge, on the phone\'s own Vulkan driver</string>
|
||||
<string name="gpuinfo_bridge_off">Not supported with the Mali bridge off: Turnip needs an Adreno (Snapdragon) GPU</string>
|
||||
|
||||
<string name="turnip_rate_limited">GitHub\'s rate limit was hit - try again later</string>
|
||||
<string name="turnip_github_http">GitHub answered HTTP %1$d</string>
|
||||
@@ -3655,6 +3658,19 @@
|
||||
<string name="stores_xbox_cloud_settings">Cloud settings</string>
|
||||
<string name="xbox_cloud_gaming" translatable="false">Xbox Cloud Gaming</string>
|
||||
<string name="xbox_filter_game_pass" translatable="false">Game Pass</string>
|
||||
<string name="setup_gpu_bridge_off">Mali bridge: off</string>
|
||||
<string name="setup_gpu_bridge_on">Mali bridge: on</string>
|
||||
<string name="setup_bridge_safe">Safe mode</string>
|
||||
<string name="setup_bridge_safe_on">Safe mode: on</string>
|
||||
<string name="setup_bridge_safe_off">Safe mode: off</string>
|
||||
<string name="setup_bridge_safe_on_detail">Most compatible. Recommended for a new device.</string>
|
||||
<string name="setup_bridge_safe_off_detail">Faster where this GPU passed its checks.</string>
|
||||
<string name="setup_bridge_dxvk">DXVK</string>
|
||||
<string name="setup_bridge_dxvk_auto">DXVK: automatic</string>
|
||||
<string name="setup_bridge_dxvk_proton">DXVK: Proton\'s own</string>
|
||||
<string name="setup_bridge_dxvk_auto_detail">Uses an older DXVK where this GPU\'s Vulkan is too old for Proton\'s.</string>
|
||||
<string name="setup_bridge_dxvk_proton_detail">Every Proton keeps its own DXVK, even where this GPU\'s Vulkan may be too old for it.</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s is not an Adreno. The experimental Vulkan bridge is on: the session draws through the phone\'s own driver. Expect bugs; send the session logs.</string>
|
||||
<string name="integration_title">Steam integration</string>
|
||||
<string name="integration_count">%1$d of %2$d on</string>
|
||||
<string name="integration_set_up">Set up</string>
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import android.content.Context
|
||||
import com.droiddeck.launcher.gpu.AppVulkan
|
||||
import com.droiddeck.launcher.gpu.GpuInfo
|
||||
import com.droiddeck.launcher.runtime.LinuxRuntime
|
||||
import com.github.luben.zstd.ZstdOutputStream
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
import java.io.File
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], manifest = Config.NONE)
|
||||
class BridgeDxvkTest {
|
||||
private val old = ComponentsManager.DXVK_FOR_VULKAN_1_1
|
||||
private val mid = ComponentsManager.DXVK_FOR_VULKAN_1_3_NO_INT64
|
||||
private lateinit var context: Context
|
||||
|
||||
private val g710 = AppVulkan(1, 1, shaderInt64 = false, maxPushConstants = 128, source = "driver")
|
||||
private val promoted = g710.copy(minor = 2, source = "bridge")
|
||||
private val full = AppVulkan(1, 3, shaderInt64 = true, maxPushConstants = 256, source = "driver")
|
||||
private val mali = GpuInfo("Mali-G710", 0, GpuInfo.Family.NOT_ADRENO, "", false)
|
||||
|
||||
@Before fun setUp() {
|
||||
context = RuntimeEnvironment.getApplication()
|
||||
File(context.filesDir, "components").deleteRecursively()
|
||||
LinuxRuntime.rootDir(context).deleteRecursively()
|
||||
}
|
||||
|
||||
@Test fun theNewestDxvkThatStartsIsChosen() {
|
||||
assertEquals(old, ComponentsManager.bridgeDxvk(g710)?.file)
|
||||
assertEquals(old, ComponentsManager.bridgeDxvk(promoted)?.file)
|
||||
assertEquals(mid, ComponentsManager.bridgeDxvk(full.copy(shaderInt64 = false))?.file)
|
||||
assertEquals(mid, ComponentsManager.bridgeDxvk(full.copy(maxPushConstants = 128))?.file)
|
||||
assertNull(ComponentsManager.bridgeDxvk(full))
|
||||
assertNull(ComponentsManager.bridgeDxvk(full.copy(shaderInt64 = null, maxPushConstants = null)))
|
||||
assertNull(ComponentsManager.bridgeDxvk(AppVulkan(1, 0, null, null, "driver")))
|
||||
assertNull(ComponentsManager.bridgeDxvk(null))
|
||||
assertEquals("Dxvk-Linux", ComponentsManager.bridgeDxvk(g710)?.release)
|
||||
assertEquals("dxvk", ComponentsManager.bridgeDxvk(g710)?.comp)
|
||||
}
|
||||
|
||||
@Test fun theDriversLinesAreRead() {
|
||||
val vk = AppVulkan.fromDriver(mapOf("device" to "Mali-G710 MC10", "api" to "1.1.177", "shader_int64" to "no", "max_push_constants" to "128"))
|
||||
assertEquals(g710, vk)
|
||||
assertNull(AppVulkan.fromDriver(mapOf("error" to "no Vulkan device")))
|
||||
assertNull(AppVulkan.fromDriver(mapOf("api" to "garbage")))
|
||||
val older = AppVulkan.fromDriver(mapOf("api" to "1.3.250"))!!
|
||||
assertNull(older.shaderInt64)
|
||||
assertNull(older.maxPushConstants)
|
||||
}
|
||||
|
||||
@Test fun theBridgesFileWinsForItsOwnGpuOnly() {
|
||||
val text = "device=Mali-G710 MC10\napi=1.2.177\ndriver_api=1.1.177\npromotion=on\nshader_int64=0\nmax_push_constants=128\n"
|
||||
val on = promoted.copy(source = "bridge, promoted")
|
||||
assertEquals(on, AppVulkan.fromBridgeFile(text, "Mali-G710 MC10"))
|
||||
assertEquals(on, AppVulkan.fromBridgeFile(text, null))
|
||||
assertEquals(on, AppVulkan.fromBridgeFile(text, "Mali-G710 MC10", "1.1.177"))
|
||||
assertNull(AppVulkan.fromBridgeFile(text, "Mali-G720 Immortalis MC12"))
|
||||
assertNull(AppVulkan.fromBridgeFile("device=x\n", null))
|
||||
}
|
||||
|
||||
@Test fun withoutPromotionOnTheDriversVersionCounts() {
|
||||
val g710File = g710.copy(source = "bridge")
|
||||
val off = "device=Mali-G710 MC10\napi=1.1.177\ndriver_api=1.1.177\npromotion=off\nshader_int64=0\nmax_push_constants=128\n"
|
||||
assertEquals(g710File, AppVulkan.fromBridgeFile(off, "Mali-G710 MC10", "1.1.177"))
|
||||
val old = "device=Mali-G710 MC10\napi=1.3.177\ndriver_api=1.1.177\nshader_int64=0\nmax_push_constants=128\n"
|
||||
assertEquals(g710File, AppVulkan.fromBridgeFile(old, "Mali-G710 MC10"))
|
||||
assertNull(AppVulkan.fromBridgeFile(off, "Mali-G710 MC10", "1.3.212"))
|
||||
}
|
||||
|
||||
@Test fun anAdrenoSessionIsNotTouched() {
|
||||
proton("proton-exp")
|
||||
assertNull(run(g710, bridge = false))
|
||||
}
|
||||
|
||||
@Test fun aShippedDxvk3IsReplacedOnAVulkan11Phone() {
|
||||
proton("proton-exp")
|
||||
proton("ge")
|
||||
val applied = mutableListOf<String>()
|
||||
val result = run(g710, applyAuto = { id, file -> applied += "$id $file"; "swapped $id" })
|
||||
assertEquals(listOf("ge $old", "proton-exp $old"), applied.sorted())
|
||||
assertTrue(result!!.startsWith("Vulkan 1.1, shaderInt64 no, push constants 128 (driver): DXVK $old"))
|
||||
}
|
||||
|
||||
@Test fun thePlayersOwnChoiceIsKept() {
|
||||
proton("proton-exp")
|
||||
writeState("proton-exp", JSONObject().put("file", "dxvk-3.1-linux.wcp").put("protonVersion", "experimental-11"))
|
||||
assertNull(run(g710))
|
||||
assertNull(run(full))
|
||||
}
|
||||
|
||||
@Test fun anAutomaticChoiceFollowsTheDevice() {
|
||||
proton("proton-exp")
|
||||
writeState("proton-exp", JSONObject().put("file", mid).put("protonVersion", "experimental-11").put("auto", true))
|
||||
val applied = mutableListOf<String>()
|
||||
run(g710, applyAuto = { id, file -> applied += "$id $file"; "swapped" })
|
||||
assertEquals(listOf("proton-exp $old"), applied)
|
||||
|
||||
writeState("proton-exp", JSONObject().put("file", old).put("protonVersion", "experimental-11").put("auto", true))
|
||||
assertNull(run(g710))
|
||||
val undone = mutableListOf<String>()
|
||||
run(full, undoAuto = { id, comp -> undone += "$id $comp"; "undone" })
|
||||
assertEquals(listOf("proton-exp dxvk"), undone)
|
||||
}
|
||||
|
||||
@Test fun theSwapAndItsUndoReachTheProton() {
|
||||
proton("proton-exp")
|
||||
val d3d11 = File(protonDir("proton-exp"), "files/lib/wine/dxvk/aarch64-windows/d3d11.dll")
|
||||
writePackage(old, "DXVK 1.10.3")
|
||||
val auto: (String, String) -> String = { id, file -> ComponentsManager.swap(context, id, file, auto = true) }
|
||||
val undo: (String, String) -> String = { id, comp -> ComponentsManager.undoAutoChoice(context, id, comp) }
|
||||
|
||||
assertNotNull(run(g710, applyAuto = auto, undoAuto = undo))
|
||||
assertEquals("DXVK 1.10.3", d3d11.readText())
|
||||
val entry = state().getJSONObject("active").getJSONObject("proton-exp").getJSONObject("dxvk")
|
||||
assertEquals(old, entry.getString("file"))
|
||||
assertTrue(entry.getBoolean("auto"))
|
||||
assertNull(run(g710, applyAuto = auto, undoAuto = undo))
|
||||
|
||||
run(full, applyAuto = auto, undoAuto = undo)
|
||||
assertEquals("bundled DXVK experimental-11", d3d11.readText())
|
||||
assertNull(state().getJSONObject("active").optJSONObject("proton-exp")?.optJSONObject("dxvk"))
|
||||
}
|
||||
|
||||
@Test fun aFailedDetectionKeepsTheLastAutomaticChoice() {
|
||||
proton("proton-exp")
|
||||
writeState("proton-exp", JSONObject().put("file", old).put("protonVersion", "experimental-11").put("auto", true))
|
||||
assertNull(run(null))
|
||||
assertEquals(old, state().getJSONObject("active").getJSONObject("proton-exp").getJSONObject("dxvk").getString("file"))
|
||||
}
|
||||
|
||||
@Test fun oneProtonFailingDoesNotStopTheOthers() {
|
||||
proton("ge")
|
||||
proton("proton-exp")
|
||||
val applied = mutableListOf<String>()
|
||||
val result = run(g710, applyAuto = { id, file -> if (id == "ge") error("disk full"); applied += "$id $file"; "swapped $id" })
|
||||
assertEquals(listOf("proton-exp $old"), applied)
|
||||
assertTrue(result!!, result.contains("disk full") && result.contains("swapped proton-exp"))
|
||||
}
|
||||
|
||||
@Test fun aChoiceWhoseSavedCopyIsGoneIsKept() {
|
||||
proton("proton-exp")
|
||||
writeState("proton-exp", JSONObject().put("file", old).put("label", "DXVK 1.10.3").put("protonVersion", "experimental-11").put("auto", true))
|
||||
val result = run(full, undoAuto = { id, comp -> ComponentsManager.undoAutoChoice(context, id, comp) })
|
||||
assertTrue(result!!, result.contains("its own copy is gone"))
|
||||
assertEquals(old, state().getJSONObject("active").getJSONObject("proton-exp").getJSONObject("dxvk").getString("file"))
|
||||
}
|
||||
|
||||
@Test fun aProtonUpdateWithNoRuleLeftJustForgetsTheChoice() {
|
||||
proton("proton-exp", "experimental-12")
|
||||
writeState("proton-exp", JSONObject().put("file", old).put("protonVersion", "experimental-11").put("auto", true))
|
||||
run(full, undoAuto = { id, comp -> ComponentsManager.undoAutoChoice(context, id, comp) })
|
||||
assertNull(state().getJSONObject("active").optJSONObject("proton-exp")?.optJSONObject("dxvk"))
|
||||
assertEquals("bundled DXVK experimental-12", File(protonDir("proton-exp"), "files/lib/wine/dxvk/aarch64-windows/d3d11.dll").readText())
|
||||
}
|
||||
|
||||
@Test fun thePlayerCanKeepProtonsOwnDxvk() {
|
||||
proton("proton-exp")
|
||||
assertNull("nothing chosen when declined", run(g710, keep = true))
|
||||
writeState("proton-exp", JSONObject().put("file", old).put("protonVersion", "experimental-11").put("auto", true))
|
||||
val undone = mutableListOf<String>()
|
||||
run(g710, keep = true, undoAuto = { id, comp -> undone += "$id $comp"; "undone" })
|
||||
assertEquals("an earlier automatic choice goes back", listOf("proton-exp dxvk"), undone)
|
||||
}
|
||||
|
||||
@Test fun turningTheBridgeOffRestoresProtonsOwn() {
|
||||
proton("proton-exp")
|
||||
val d3d11 = File(protonDir("proton-exp"), "files/lib/wine/dxvk/aarch64-windows/d3d11.dll")
|
||||
writePackage(old, "DXVK 1.10.3")
|
||||
val auto: (String, String) -> String = { id, file -> ComponentsManager.swap(context, id, file, auto = true) }
|
||||
val undo: (String, String) -> String = { id, comp -> ComponentsManager.undoAutoChoice(context, id, comp) }
|
||||
run(g710, applyAuto = auto, undoAuto = undo)
|
||||
assertEquals("DXVK 1.10.3", d3d11.readText())
|
||||
run(null, bridge = false, applyAuto = auto, undoAuto = undo)
|
||||
assertEquals("bundled DXVK experimental-11", d3d11.readText())
|
||||
assertNull(state().getJSONObject("active").optJSONObject("proton-exp")?.optJSONObject("dxvk"))
|
||||
}
|
||||
|
||||
@Test fun turningTheBridgeOffKeepsThePlayersOwnChoice() {
|
||||
proton("proton-exp")
|
||||
writeState("proton-exp", JSONObject().put("file", "dxvk-3.1-linux.wcp").put("protonVersion", "experimental-11"))
|
||||
assertNull(run(g710, bridge = false))
|
||||
assertNull(run(g710, keep = true))
|
||||
}
|
||||
|
||||
@Test fun aQueuedAutomaticSwapIsDroppedWhenTheBridgeIsOff() {
|
||||
proton("proton-exp")
|
||||
val f = File(context.filesDir, "components/state.json")
|
||||
f.parentFile!!.mkdirs()
|
||||
f.writeText(JSONObject().put("active", JSONObject())
|
||||
.put("queued", JSONObject().put("proton-exp", JSONObject()
|
||||
.put("dxvk", JSONObject().put("kind", "package").put("file", old).put("auto", true))
|
||||
.put("vkd3d", JSONObject().put("kind", "package").put("file", "vkd3d-x.wcp")))).toString())
|
||||
assertNull(run(null, bridge = false))
|
||||
val q = state().getJSONObject("queued").getJSONObject("proton-exp")
|
||||
assertFalse(q.has("dxvk"))
|
||||
assertTrue("the player's own queued swap stays", q.has("vkd3d"))
|
||||
}
|
||||
|
||||
@Test fun whenAutomaticChoicesAreUndone() {
|
||||
val rule = ComponentsManager.bridgeDxvk(g710)
|
||||
assertTrue(ComponentsManager.autoDxvkUndoWanted(false, false, null, null))
|
||||
assertTrue(ComponentsManager.autoDxvkUndoWanted(true, true, g710, null))
|
||||
assertTrue(ComponentsManager.autoDxvkUndoWanted(true, false, full, null))
|
||||
assertFalse("failed detection keeps it", ComponentsManager.autoDxvkUndoWanted(true, false, null, null))
|
||||
assertFalse(ComponentsManager.autoDxvkUndoWanted(true, false, g710, rule))
|
||||
}
|
||||
|
||||
private fun run(
|
||||
vk: AppVulkan?,
|
||||
bridge: Boolean = true,
|
||||
keep: Boolean = false,
|
||||
applyAuto: (String, String) -> String = { id, file -> error("apply $id $file") },
|
||||
undoAuto: (String, String) -> String = { id, comp -> error("undo $id $comp") },
|
||||
) = ComponentsManager.ensureForcedPackages(
|
||||
context, mali,
|
||||
lookup = { file, release -> ComponentsManager.CatalogItem(file, "dxvk", release, "https://example.invalid/$file", 4, "sha256:" + "ab".repeat(32)) },
|
||||
fetch = {},
|
||||
apply = { id, file -> error("non-automatic apply $id $file") },
|
||||
vulkan = vk, bridgeSession = bridge, applyAuto = applyAuto, undoAuto = undoAuto, keepProtonDxvk = keep,
|
||||
)
|
||||
|
||||
private fun protonDir(name: String) = File(LinuxRuntime.rootDir(context), "root/.local/share/Steam/steamapps/common/$name")
|
||||
|
||||
private fun proton(name: String, version: String = "experimental-11") {
|
||||
val dir = protonDir(name)
|
||||
File(dir, "files/lib/wine").mkdirs()
|
||||
File(dir, "proton").writeText("#!/bin/sh\n")
|
||||
File(dir, "version").writeText("1 $version\n")
|
||||
File(dir, "files/lib/wine/dxvk/aarch64-windows/d3d11.dll").apply {
|
||||
parentFile!!.mkdirs()
|
||||
writeText("bundled DXVK $version")
|
||||
}
|
||||
}
|
||||
|
||||
private fun state() = JSONObject(File(context.filesDir, "components/state.json").readText())
|
||||
|
||||
private fun writeState(protonId: String, dxvk: JSONObject) {
|
||||
val f = File(context.filesDir, "components/state.json")
|
||||
f.parentFile!!.mkdirs()
|
||||
f.writeText(JSONObject().put("active", JSONObject().put(protonId, JSONObject().put("dxvk", dxvk)))
|
||||
.put("queued", JSONObject()).toString())
|
||||
}
|
||||
|
||||
private fun writePackage(file: String, d3d11: String) {
|
||||
val dest = File(context.filesDir, "components/packages/$file")
|
||||
dest.parentFile!!.mkdirs()
|
||||
TarArchiveOutputStream(ZstdOutputStream(dest.outputStream())).use { tar ->
|
||||
fun put(name: String, bytes: ByteArray) {
|
||||
tar.putArchiveEntry(TarArchiveEntry(name).apply { size = bytes.size.toLong() })
|
||||
tar.write(bytes)
|
||||
tar.closeArchiveEntry()
|
||||
}
|
||||
put("./profile.json", JSONObject().put("type", "DXVK-Linux").put("versionName", file.removeSuffix(".wcp")).toString().toByteArray())
|
||||
put("./files/lib/wine/dxvk/aarch64-windows/d3d11.dll", d3d11.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user