diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ca6bf75b9c..157c96cf04 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -166,6 +166,16 @@ android { // 'extractNativeLibs' was not enough to keep the jniLibs and // the libs went missing after adding on-demand feature delivery useLegacyPackaging = true + + // adrenotools' hook libs are present both as prebuilts in jniLibs/ + // and as outputs of the vulkan_renderer CMake subproject. Keep the + // first one merge encounters (the prebuilt) so packaging doesn't fail. + pickFirsts += setOf( + "**/libhook_impl.so", + "**/libmain_hook.so", + "**/libfile_redirect_hook.so", + "**/libgsl_alloc_hook.so", + ) } } testOptions { @@ -204,6 +214,14 @@ android { // } // } + // Phase A — Vulkan compositor (arm64-v8a only; armeabi-v7a is a no-op in the CMakeLists) + externalNativeBuild { + cmake { + path = file("src/main/cpp/vulkan_renderer/CMakeLists.txt") + version = "3.22.1" + } + } + // (For now) Uncomment for LeakCanary to work. // configurations { // debugImplementation { diff --git a/app/src/main/cpp/vulkan_renderer/CMakeLists.txt b/app/src/main/cpp/vulkan_renderer/CMakeLists.txt new file mode 100644 index 0000000000..ded7498ba0 --- /dev/null +++ b/app/src/main/cpp/vulkan_renderer/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.22.1) +project(VulkanRenderer) + +# adrenotools explicitly rejects non-arm64 ABIs with a FATAL_ERROR in its CMakeLists. +# The Vulkan compositor itself also targets arm64 devices only. +# For armeabi-v7a builds, this file is a no-op. +if(NOT ${CMAKE_ANDROID_ARCH_ABI} STREQUAL "arm64-v8a") + message(STATUS "vulkan_renderer: skipping non-arm64 ABI (${CMAKE_ANDROID_ARCH_ABI})") + return() +endif() + +set(RENDERER_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../winlator) +set(ADRENOTOOLS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../extras/adrenotools) + +# adrenotools is needed for Phase B (Turnip driver injection on Adreno). +# In Phase A it is linked but its entry-point (adrenotools_open_libvulkan) is only +# called when a custom Turnip driver path is configured. On Mali and other non-Adreno +# GPUs the standard system Vulkan driver is used instead. +add_subdirectory(${ADRENOTOOLS_DIR} ${CMAKE_CURRENT_BINARY_DIR}/adrenotools_build) + +# SPIR-V shader headers are pre-compiled and checked into the source tree alongside +# their .vert / .frag sources. glslangValidator is NOT required at build time. +# To regenerate after shader changes, run from the repo root: +# +# GLSLANG="$ANDROID_SDK_ROOT/emulator/lib64/vulkan/glslangValidator" +# SRC=app/src/main/cpp/winlator +# $GLSLANG -V --vn window_vert_code -x -o $SRC/window_vert.h $SRC/window.vert +# $GLSLANG -V --vn window_frag_code -x -o $SRC/window_frag.h $SRC/window.frag + +add_library(vulkan_renderer SHARED + ${RENDERER_SRC_DIR}/vulkan_jni.cpp + ${RENDERER_SRC_DIR}/VulkanRendererContext.cpp +) + +target_include_directories(vulkan_renderer PRIVATE + # Finds VulkanRendererContext.h, window_vert.h, window_frag.h + ${RENDERER_SRC_DIR} +) + +target_compile_options(vulkan_renderer PRIVATE + -std=c++17 + -fvisibility=hidden + -Wall + -Wextra +) + +target_link_libraries(vulkan_renderer + log + android + vulkan + adrenotools + dl + atomic +) diff --git a/app/src/main/cpp/winlator/VulkanRendererContext.cpp b/app/src/main/cpp/winlator/VulkanRendererContext.cpp new file mode 100644 index 0000000000..fe90fa9eff --- /dev/null +++ b/app/src/main/cpp/winlator/VulkanRendererContext.cpp @@ -0,0 +1,1832 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#include "VulkanRendererContext.h" +#include +#include +#include +#include +#include +#include +#include +#include "window_vert.h" +#include "window_frag.h" + +VulkanRendererContext::VulkanRendererContext(ANativeWindow* win, int cW, int cH, void* aHandle) + : window(win), surfaceWidth(cW), surfaceHeight(cH), containerWidth(cW), containerHeight(cH), + adrenotoolsHandle(aHandle) +{ + createInstance(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); + createSwapchain(); createRenderPass(); createDSLayout(); + createPipeline(false, pipeline); createCursorPipeline(); + createFramebuffers(); createCmdPool(); createSampler(); + createWinTexPool(); createCursorDS(); createCmdBufs(); createSyncObjects(); + VkFenceCreateInfo ofi{}; ofi.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; ofi.flags = VK_FENCE_CREATE_SIGNALED_BIT; + if (vk_.CreateFence(device, &ofi, nullptr, &oneTimeFence) != VK_SUCCESS) { + throw std::runtime_error("oneTimeFence"); + } + isRunning = true; + renderThread = std::thread(&VulkanRendererContext::renderLoop, this); +} + +VulkanRendererContext::~VulkanRendererContext() { + isRunning = false; dirtyCV.notify_all(); + if (renderThread.joinable()) renderThread.join(); + std::lock_guard lk(renderMutex); + vk_.DeviceWaitIdle(device); + // texMap entries with isAHB=true are aliases into ahbImportCache; the + // backing resources are freed by cleanupAllAHBCache below. Only destroy + // the non-AHB CPU textures here to avoid a double-free. + for (auto& [id, wt] : texMap) { if (!wt.isAHB) destroyWinTex(wt); } + texMap.clear(); + cleanupSwapchain(); cleanupCursorTex(); + cleanupAllAHBCache(); + vk_.DestroySampler(device, sampler, nullptr); + vk_.DestroyDescriptorPool(device, winTexPool, nullptr); + if (cursorPool != VK_NULL_HANDLE) vk_.DestroyDescriptorPool(device, cursorPool, nullptr); + if (cursorPipe != VK_NULL_HANDLE) vk_.DestroyPipeline(device, cursorPipe, nullptr); + vk_.DestroyPipeline(device, pipeline, nullptr); + vk_.DestroyPipelineLayout(device, pipeLayout, nullptr); + vk_.DestroyDescriptorSetLayout(device, dsLayout, nullptr); + for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vk_.DestroySemaphore(device, renderDoneSems[i], nullptr); + vk_.DestroySemaphore(device, imgAvailSems[i], nullptr); + vk_.DestroyFence(device, inFlightFences[i], nullptr); + } + if (scanoutBlitFence != VK_NULL_HANDLE) { + vk_.WaitForFences(device, 1, &scanoutBlitFence, VK_TRUE, UINT64_MAX); + vk_.DestroyFence(device, scanoutBlitFence, nullptr); + scanoutBlitFence = VK_NULL_HANDLE; + } + if (scanoutBlitCb != VK_NULL_HANDLE) { + vk_.FreeCommandBuffers(device, cmdPool, 1, &scanoutBlitCb); + scanoutBlitCb = VK_NULL_HANDLE; + } + if (oneTimeFence != VK_NULL_HANDLE) { + vk_.DestroyFence(device, oneTimeFence, nullptr); + oneTimeFence = VK_NULL_HANDLE; + } + vk_.DestroyCommandPool(device, cmdPool, nullptr); + vk_.DestroyRenderPass(device, renderPass, nullptr); + vk_.DestroyDevice(device, nullptr); + vk_.DestroySurfaceKHR(instance, surface, nullptr); + vk_.DestroyInstance(instance, nullptr); + if (adrenotoolsHandle) { dlclose(adrenotoolsHandle); adrenotoolsHandle = nullptr; } +} + +void VulkanRendererContext::loadInstanceDispatch() { + auto i = [&](const char* name) { return gipa ? gipa(instance, name) : nullptr; }; +#define LOAD_I2(fn) vk_.fn = (PFN_vk##fn)i("vk"#fn) + LOAD_I2(DestroyInstance); + LOAD_I2(EnumeratePhysicalDevices); + LOAD_I2(GetPhysicalDeviceProperties); + LOAD_I2(GetPhysicalDeviceMemoryProperties); + LOAD_I2(GetPhysicalDeviceSurfaceCapabilitiesKHR); + LOAD_I2(GetPhysicalDeviceSurfaceFormatsKHR); + LOAD_I2(GetPhysicalDeviceSurfacePresentModesKHR); + LOAD_I2(GetPhysicalDeviceQueueFamilyProperties); + LOAD_I2(GetPhysicalDeviceSurfaceSupportKHR); + LOAD_I2(CreateDevice); + LOAD_I2(DestroySurfaceKHR); + LOAD_I2(CreateAndroidSurfaceKHR); + LOAD_I2(GetDeviceProcAddr); +} +void VulkanRendererContext::loadDeviceDispatch() { + auto d = [&](const char* name) -> PFN_vkVoidFunction { + return vk_.GetDeviceProcAddr ? vk_.GetDeviceProcAddr(device, name) : nullptr; + }; +#define LOAD_D2(fn) vk_.fn = (PFN_vk##fn)d("vk"#fn) + LOAD_D2(DestroyDevice); + LOAD_D2(GetDeviceQueue); + LOAD_D2(DeviceWaitIdle); + LOAD_D2(CreateSwapchainKHR); + LOAD_D2(DestroySwapchainKHR); + LOAD_D2(GetSwapchainImagesKHR); + LOAD_D2(AcquireNextImageKHR); + LOAD_D2(QueuePresentKHR); + LOAD_D2(QueueSubmit); + LOAD_D2(CreateRenderPass); + LOAD_D2(DestroyRenderPass); + LOAD_D2(CreateFramebuffer); + LOAD_D2(DestroyFramebuffer); + LOAD_D2(CreateImageView); + LOAD_D2(DestroyImageView); + LOAD_D2(CreateImage); + LOAD_D2(DestroyImage); + LOAD_D2(CreateBuffer); + LOAD_D2(DestroyBuffer); + LOAD_D2(AllocateMemory); + LOAD_D2(FreeMemory); + LOAD_D2(MapMemory); + LOAD_D2(FlushMappedMemoryRanges); + LOAD_D2(BindBufferMemory); + LOAD_D2(BindImageMemory); + LOAD_D2(GetBufferMemoryRequirements); + LOAD_D2(GetImageMemoryRequirements); + LOAD_D2(CreateDescriptorSetLayout); + LOAD_D2(DestroyDescriptorSetLayout); + LOAD_D2(CreateDescriptorPool); + LOAD_D2(DestroyDescriptorPool); + LOAD_D2(AllocateDescriptorSets); + LOAD_D2(FreeDescriptorSets); + LOAD_D2(UpdateDescriptorSets); + LOAD_D2(CreatePipelineLayout); + LOAD_D2(DestroyPipelineLayout); + LOAD_D2(CreateShaderModule); + LOAD_D2(DestroyShaderModule); + LOAD_D2(CreateGraphicsPipelines); + LOAD_D2(DestroyPipeline); + LOAD_D2(CreateCommandPool); + LOAD_D2(DestroyCommandPool); + LOAD_D2(AllocateCommandBuffers); + LOAD_D2(FreeCommandBuffers); + LOAD_D2(BeginCommandBuffer); + LOAD_D2(EndCommandBuffer); + LOAD_D2(ResetCommandBuffer); + LOAD_D2(CmdBeginRenderPass); + LOAD_D2(CmdEndRenderPass); + LOAD_D2(CmdBindPipeline); + LOAD_D2(CmdBindDescriptorSets); + LOAD_D2(CmdDraw); + LOAD_D2(CmdPushConstants); + LOAD_D2(CmdSetViewport); + LOAD_D2(CmdSetScissor); + LOAD_D2(CmdPipelineBarrier); + LOAD_D2(CmdCopyImage); + LOAD_D2(CmdCopyBufferToImage); + LOAD_D2(CreateSampler); + LOAD_D2(DestroySampler); + LOAD_D2(CreateSemaphore); + LOAD_D2(DestroySemaphore); + LOAD_D2(CreateFence); + LOAD_D2(DestroyFence); + LOAD_D2(WaitForFences); + LOAD_D2(ResetFences); + LOAD_D2(GetFenceStatus); + + vk_.GetAndroidHardwareBufferPropertiesANDROID = + (PFN_vkGetAndroidHardwareBufferPropertiesANDROID)d("vkGetAndroidHardwareBufferPropertiesANDROID"); +} + +void VulkanRendererContext::createInstance() { + RLOG("createInstance: adrenotoolsHandle=%p (custom driver %s)", + adrenotoolsHandle, adrenotoolsHandle?"ACTIVE":"NOT SET - using stock driver"); + + if (adrenotoolsHandle) { + gipa = (PFN_vkGetInstanceProcAddr)dlsym(adrenotoolsHandle, "vkGetInstanceProcAddr"); + } + if (!gipa) { + void* loaderLib = dlopen("libvulkan.so", RTLD_NOW | RTLD_GLOBAL); + if (loaderLib) + gipa = (PFN_vkGetInstanceProcAddr)dlsym(loaderLib, "vkGetInstanceProcAddr"); + } + + vk_.CreateInstance = (PFN_vkCreateInstance)gipa(nullptr, "vkCreateInstance"); + VkApplicationInfo ai{}; ai.sType=VK_STRUCTURE_TYPE_APPLICATION_INFO; + ai.pApplicationName="Winlator"; ai.apiVersion=VK_API_VERSION_1_3; + const char* ext[]={"VK_KHR_surface","VK_KHR_android_surface"}; + VkInstanceCreateInfo ci{}; ci.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + ci.pApplicationInfo=&ai; ci.enabledExtensionCount=2; ci.ppEnabledExtensionNames=ext; + if (vk_.CreateInstance(&ci,nullptr,&instance)!=VK_SUCCESS) throw std::runtime_error("instance"); + + loadInstanceDispatch(); +} +void VulkanRendererContext::createSurface() { + VkAndroidSurfaceCreateInfoKHR ci{}; ci.sType=VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + ci.window=window; + if (vk_.CreateAndroidSurfaceKHR(instance,&ci,nullptr,&surface)!=VK_SUCCESS) throw std::runtime_error("surface"); +} +void VulkanRendererContext::pickPhysicalDevice() { + uint32_t n=0; vk_.EnumeratePhysicalDevices(instance,&n,nullptr); + std::vector devs(n); vk_.EnumeratePhysicalDevices(instance,&n,devs.data()); + physicalDevice = VK_NULL_HANDLE; + graphicsQueueFamilyIndex = 0; + for (auto d : devs) { + uint32_t qCount = 0; + vk_.GetPhysicalDeviceQueueFamilyProperties(d, &qCount, nullptr); + std::vector qProps(qCount); + vk_.GetPhysicalDeviceQueueFamilyProperties(d, &qCount, qProps.data()); + for (uint32_t i = 0; i < qCount; i++) { + VkBool32 present = VK_FALSE; + vk_.GetPhysicalDeviceSurfaceSupportKHR(d, i, surface, &present); + if ((qProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && present) { + physicalDevice = d; + graphicsQueueFamilyIndex = i; + return; + } + } + } + if (n > 0) physicalDevice = devs[0]; +} +void VulkanRendererContext::createLogicalDevice() { + float p=1.f; + VkDeviceQueueCreateInfo qi{}; qi.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + qi.queueFamilyIndex=graphicsQueueFamilyIndex; qi.queueCount=1; qi.pQueuePriorities=&p; + + PFN_vkEnumerateDeviceExtensionProperties enumDevExts = + (PFN_vkEnumerateDeviceExtensionProperties)gipa(instance, "vkEnumerateDeviceExtensionProperties"); + { uint32_t n=0; if(enumDevExts) enumDevExts(physicalDevice,nullptr,&n,nullptr); + std::vector av(n); + if(enumDevExts) enumDevExts(physicalDevice,nullptr,&n,av.data()); + for (auto& e:av) { + if (strcmp(e.extensionName,"VK_EXT_filter_cubic")==0 + || strcmp(e.extensionName,"VK_IMG_filter_cubic")==0) cubicSupported=true; + } } + std::vector extList = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME + }; + if (cubicSupported) extList.push_back("VK_EXT_filter_cubic"); + VkDeviceCreateInfo ci{}; ci.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + ci.pQueueCreateInfos=&qi; ci.queueCreateInfoCount=1; + ci.enabledExtensionCount=(uint32_t)extList.size(); ci.ppEnabledExtensionNames=extList.data(); + if (vk_.CreateDevice(physicalDevice,&ci,nullptr,&device)!=VK_SUCCESS) throw std::runtime_error("device"); + vk_.GetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)gipa(instance, "vkGetDeviceProcAddr"); + loadDeviceDispatch(); + vk_.GetDeviceQueue(device,graphicsQueueFamilyIndex,0,&graphicsQueue); + + vk_.GetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + VkPhysicalDeviceProperties props{}; + vk_.GetPhysicalDeviceProperties(physicalDevice, &props); + maxAnisotropy = props.limits.maxSamplerAnisotropy; +} +void VulkanRendererContext::createSwapchain() { + VkSurfaceCapabilitiesKHR caps; + vk_.GetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice,surface,&caps); + swapchainExt=(caps.currentExtent.width!=0xFFFFFFFF)?caps.currentExtent:VkExtent2D{(uint32_t)surfaceWidth,(uint32_t)surfaceHeight}; + uint32_t fmtN=0; vk_.GetPhysicalDeviceSurfaceFormatsKHR(physicalDevice,surface,&fmtN,nullptr); + std::vector fmts(fmtN); vk_.GetPhysicalDeviceSurfaceFormatsKHR(physicalDevice,surface,&fmtN,fmts.data()); + swapchainFmt = VK_FORMAT_R8G8B8A8_UNORM; + uint32_t imgCount=caps.minImageCount+1; + + uint32_t pmCount=0; + vk_.GetPhysicalDeviceSurfacePresentModesKHR(physicalDevice,surface,&pmCount,nullptr); + availablePresentModes.resize(pmCount); + vk_.GetPhysicalDeviceSurfacePresentModesKHR(physicalDevice,surface,&pmCount,availablePresentModes.data()); + VkPresentModeKHR presentMode=VK_PRESENT_MODE_FIFO_KHR; + for (auto pm:availablePresentModes) if(pm==requestedPresentMode){presentMode=pm;break;} + + const char* swapchainEnv = std::getenv("RENDERER_SWAPCHAIN"); + uint32_t swapchainExtra = 0; + if (swapchainEnv && *swapchainEnv) { + int parsed = std::atoi(swapchainEnv); + if (parsed > 0) swapchainExtra = (uint32_t)parsed; + } + imgCount += swapchainExtra; + + if (presentMode == VK_PRESENT_MODE_MAILBOX_KHR && imgCount < 3) imgCount = 3; + if (imgCount < caps.minImageCount) imgCount = caps.minImageCount; + if (caps.maxImageCount > 0 && imgCount > caps.maxImageCount) imgCount = caps.maxImageCount; + if(verboseLog){ + std::string pmList; + for(auto pm:availablePresentModes) pmList+=std::to_string((int)pm)+" "; + RLOG("createSwapchain: %dx%d fmt=%d supportedPresentModes=[%s] chosen=%d req=%d", + swapchainExt.width,swapchainExt.height,(int)swapchainFmt,pmList.c_str(),(int)presentMode,(int)requestedPresentMode); + } + + VkSurfaceTransformFlagBitsKHR pre= + (caps.supportedTransforms&VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)? + VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR:caps.currentTransform; + + VkCompositeAlphaFlagBitsKHR compositeAlpha= + (caps.supportedCompositeAlpha&VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)? + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR:VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + + VkSwapchainKHR oldSwapchain=swapchain; + VkSwapchainCreateInfoKHR ci{}; ci.sType=VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + ci.surface=surface; ci.minImageCount=imgCount; ci.imageFormat=swapchainFmt; + ci.imageColorSpace=VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; ci.imageExtent=swapchainExt; + ci.imageArrayLayers=1; ci.imageUsage=VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + ci.imageSharingMode=VK_SHARING_MODE_EXCLUSIVE; ci.preTransform=pre; + ci.compositeAlpha=compositeAlpha; ci.presentMode=presentMode; ci.clipped=VK_TRUE; + ci.oldSwapchain=oldSwapchain; + if (vk_.CreateSwapchainKHR(device,&ci,nullptr,&swapchain)!=VK_SUCCESS) throw std::runtime_error("swapchain"); + RLOG("swapchain created: %dx%d format=%d presentMode=%d compositeAlpha=%d imageCount=%u extra=%u", + swapchainExt.width,swapchainExt.height,swapchainFmt,(int)presentMode,(int)compositeAlpha,imgCount,swapchainExtra); + if (oldSwapchain!=VK_NULL_HANDLE) vk_.DestroySwapchainKHR(device,oldSwapchain,nullptr); + vk_.GetSwapchainImagesKHR(device,swapchain,&imgCount,nullptr); + swapchainImages.resize(imgCount); vk_.GetSwapchainImagesKHR(device,swapchain,&imgCount,swapchainImages.data()); + swapchainViews.resize(imgCount); + for (size_t i=0;i(emi.pNext); + emi.pNext=&ef; + + VkImageCreateInfo ii{}; + ii.sType=VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + ii.pNext=&emi; ii.imageType=VK_IMAGE_TYPE_2D; + ii.format=(swapRB)?VK_FORMAT_R8G8B8A8_UNORM:VK_FORMAT_B8G8R8A8_UNORM; + ii.extent={desc.width,desc.height,1}; + ii.mipLevels=1; + ii.arrayLayers=1; + ii.samples=VK_SAMPLE_COUNT_1_BIT; + ii.tiling=VK_IMAGE_TILING_OPTIMAL; + ii.usage=VK_IMAGE_USAGE_SAMPLED_BIT; + ii.sharingMode=VK_SHARING_MODE_EXCLUSIVE; + ii.initialLayout=VK_IMAGE_LAYOUT_UNDEFINED; + + if (vk_.CreateImage(device,&ii,nullptr,&wt.img)!=VK_SUCCESS) + return false; + + VkImportAndroidHardwareBufferInfoANDROID imp{}; + imp.sType=VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID; + imp.buffer=ahb; + + VkMemoryDedicatedAllocateInfo ded{}; + ded.sType=VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; + ded.pNext=&imp; + ded.image=wt.img; + + VkMemoryAllocateInfo mai{}; + mai.sType=VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + mai.pNext=&ded; + mai.allocationSize=props.allocationSize; + mai.memoryTypeIndex=findMemType(props.memoryTypeBits,0); + if (vk_.AllocateMemory(device,&mai,nullptr,&wt.mem)!=VK_SUCCESS){ + vk_.DestroyImage(device,wt.img,nullptr); + wt.img=VK_NULL_HANDLE; + return false; + } + + vk_.BindImageMemory(device,wt.img,wt.mem,0); + VkExternalFormatANDROID vef{}; + vef.sType=VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID; + vef.externalFormat=(swapRB)?VK_FORMAT_R8G8B8A8_UNORM:VK_FORMAT_B8G8R8A8_UNORM; + + VkImageViewCreateInfo vi{}; + vi.sType=VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + vi.pNext=&vef; + vi.image=wt.img; + vi.viewType=VK_IMAGE_VIEW_TYPE_2D; + vi.format=(swapRB)?VK_FORMAT_R8G8B8A8_UNORM:VK_FORMAT_B8G8R8A8_UNORM; + vi.components={VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY}; + vi.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}; + if (vk_.CreateImageView(device,&vi,nullptr,&wt.view)!=VK_SUCCESS){ + destroyWinTex(wt); + return false; + } + + VkDescriptorSetAllocateInfo dsai{}; + dsai.sType=VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + dsai.descriptorPool=winTexPool; + dsai.descriptorSetCount=1; + dsai.pSetLayouts=&dsLayout; + VkResult dsRes = vk_.AllocateDescriptorSets(device,&dsai,&wt.ds); + if (dsRes==VK_ERROR_OUT_OF_POOL_MEMORY) { + RLOG_E("importAHBToWinTex: descriptor pool exhausted for AHB texture"); + destroyWinTex(wt); + return false; + } + if (dsRes!=VK_SUCCESS){ + destroyWinTex(wt); + return false; + } + VkDescriptorImageInfo dii{}; + dii.imageLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + dii.imageView=wt.view; + dii.sampler=sampler; + + VkWriteDescriptorSet wr{}; + wr.sType=VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + wr.dstSet=wt.ds; + wr.dstBinding=0; + wr.descriptorType=VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + wr.descriptorCount=1; + wr.pImageInfo=&dii; + vk_.UpdateDescriptorSets(device,1,&wr,0,nullptr); + wt.needsTransition = true; + wt.isAHB=true; + wt.w=(int)desc.width; + wt.h=(int)desc.height; + + return true; +} + +void VulkanRendererContext::destroyWinTex(WinTex& wt) { + if (wt.img!=VK_NULL_HANDLE || wt.stg!=VK_NULL_HANDLE) { + deleteQueue.push_back(wt); + } + wt={}; +} + +void VulkanRendererContext::ensureCursorTex(short w, short h) { + if (cursorImg!=VK_NULL_HANDLE && cursorTexW==w && cursorTexH==h) return; + cleanupCursorTex(); + VkImageCreateInfo ii{}; ii.sType=VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; ii.imageType=VK_IMAGE_TYPE_2D; + ii.extent={(uint32_t)w,(uint32_t)h,1}; ii.mipLevels=1; ii.arrayLayers=1; ii.format=VK_FORMAT_B8G8R8A8_UNORM; + ii.tiling=VK_IMAGE_TILING_OPTIMAL; ii.initialLayout=VK_IMAGE_LAYOUT_UNDEFINED; + ii.usage=VK_IMAGE_USAGE_TRANSFER_DST_BIT|VK_IMAGE_USAGE_SAMPLED_BIT; ii.samples=VK_SAMPLE_COUNT_1_BIT; ii.sharingMode=VK_SHARING_MODE_EXCLUSIVE; + vk_.CreateImage(device,&ii,nullptr,&cursorImg); + VkMemoryRequirements req; vk_.GetImageMemoryRequirements(device,cursorImg,&req); + VkMemoryAllocateInfo ai{}; ai.sType=VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; ai.allocationSize=req.size; ai.memoryTypeIndex=findMemType(req.memoryTypeBits,VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + vk_.AllocateMemory(device,&ai,nullptr,&cursorMem); vk_.BindImageMemory(device,cursorImg,cursorMem,0); + VkImageViewCreateInfo vi{}; vi.sType=VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; vi.image=cursorImg; vi.viewType=VK_IMAGE_VIEW_TYPE_2D; vi.format=VK_FORMAT_B8G8R8A8_UNORM; vi.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}; + vk_.CreateImageView(device,&vi,nullptr,&cursorView); + VkDescriptorImageInfo dii{}; dii.imageLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; dii.imageView=cursorView; dii.sampler=sampler; + VkWriteDescriptorSet wr{}; wr.sType=VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; wr.dstSet=cursorDS; wr.dstBinding=0; wr.descriptorType=VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; wr.descriptorCount=1; wr.pImageInfo=&dii; + vk_.UpdateDescriptorSets(device,1,&wr,0,nullptr); + auto cb=beginOneTime(); + transition(cb,cursorImg,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + 0,VK_ACCESS_SHADER_READ_BIT,VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + endOneTime(cb); + cursorTexW=w; cursorTexH=h; +} +void VulkanRendererContext::cleanupCursorTex() { + if (cursorView!=VK_NULL_HANDLE){vk_.DestroyImageView(device,cursorView,nullptr);cursorView=VK_NULL_HANDLE;} + if (cursorImg!=VK_NULL_HANDLE){vk_.DestroyImage(device,cursorImg,nullptr);cursorImg=VK_NULL_HANDLE;} + if (cursorMem!=VK_NULL_HANDLE){vk_.FreeMemory(device,cursorMem,nullptr);cursorMem=VK_NULL_HANDLE;} + if (cursorStg!=VK_NULL_HANDLE){vk_.DestroyBuffer(device,cursorStg,nullptr);vk_.FreeMemory(device,cursorStgM,nullptr);cursorStg=VK_NULL_HANDLE;cursorStgP=nullptr;cursorStgC=0;} + cursorTexW=0; cursorTexH=0; +} +void VulkanRendererContext::ensureCursorStaging(VkDeviceSize sz) { + if (cursorStgC>=sz) return; + if (cursorStg!=VK_NULL_HANDLE){vk_.DestroyBuffer(device,cursorStg,nullptr);vk_.FreeMemory(device,cursorStgM,nullptr);} + createBuffer(sz,VK_BUFFER_USAGE_TRANSFER_SRC_BIT,VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,cursorStg,cursorStgM); + vk_.MapMemory(device,cursorStgM,0,sz,0,&cursorStgP); cursorStgC=sz; +} + +void VulkanRendererContext::recordCmdBuf(VkCommandBuffer cb, uint32_t imgIdx, + const std::vector& draws, + VkBuffer cursorUpload, bool hasCursorUpload, + float ox, float oy, float sx, float sy, float cw, float ch, + short ptrX, short ptrY, short curHotX, short curHotY, + short curW, short curH, bool curVis) +{ + VkCommandBufferBeginInfo bi{}; bi.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + if (vk_.BeginCommandBuffer(cb,&bi)!=VK_SUCCESS) throw std::runtime_error("begin cb"); + + // Reuse class-member scratch vectors instead of reallocating each frame. + // ahbTransitions: one-time UNDEFINED->SHADER_READ_ONLY for newly-imported AHBs. + // preUpload/postUpload: UNDEFINED->TRANSFER_DST then TRANSFER_DST->SHADER_READ + // around a staging-buffer copy for CPU-uploaded textures. + auto& ahbTransitions = frameAhbTransitions; + auto& preUpload = framePreUpload; + auto& postUpload = framePostUpload; + ahbTransitions.clear(); + preUpload.clear(); + postUpload.clear(); + ahbTransitions.reserve(draws.size()); + preUpload.reserve(draws.size()); + postUpload.reserve(draws.size() + (hasCursorUpload ? 1 : 0)); + for (auto& d:draws) { + if (d.img==VK_NULL_HANDLE) continue; + if (d.isAHB && d.needsTransition) { + VkImageMemoryBarrier b{}; b.sType=VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + b.oldLayout=VK_IMAGE_LAYOUT_UNDEFINED; + b.newLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + b.srcQueueFamilyIndex=b.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED; + b.image=d.img; b.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}; + b.srcAccessMask=0; b.dstAccessMask=VK_ACCESS_SHADER_READ_BIT; + ahbTransitions.push_back(b); + } else if (!d.isAHB && (d.needsTransition || d.upload!=VK_NULL_HANDLE)) { + VkImageMemoryBarrier b{}; b.sType=VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + b.oldLayout=VK_IMAGE_LAYOUT_UNDEFINED; + b.newLayout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + b.srcQueueFamilyIndex=b.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED; + b.image=d.img; b.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}; + b.srcAccessMask=0; + b.dstAccessMask=VK_ACCESS_TRANSFER_WRITE_BIT; + preUpload.push_back(b); + b.oldLayout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + b.newLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + b.srcAccessMask=VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask=VK_ACCESS_SHADER_READ_BIT; + postUpload.push_back(b); + } + } + if (!ahbTransitions.empty()) + vk_.CmdPipelineBarrier(cb,VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0,0,nullptr,0,nullptr,(uint32_t)ahbTransitions.size(),ahbTransitions.data()); + if (!preUpload.empty()) + vk_.CmdPipelineBarrier(cb, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0,0,nullptr,0,nullptr,(uint32_t)preUpload.size(),preUpload.data()); + for (auto& d:draws) { + if (d.isAHB||d.upload==VK_NULL_HANDLE||d.img==VK_NULL_HANDLE) continue; + VkBufferImageCopy r{}; r.bufferOffset=0; r.bufferRowLength=0; r.bufferImageHeight=0; + r.imageSubresource={VK_IMAGE_ASPECT_COLOR_BIT,0,0,1}; + r.imageExtent={(uint32_t)d.w,(uint32_t)d.h,1}; + vk_.CmdCopyBufferToImage(cb,d.upload,d.img,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&r); + } + bool hasCursorCopy=hasCursorUpload&&cursorImg!=VK_NULL_HANDLE&&cursorUpload!=VK_NULL_HANDLE; + if (hasCursorCopy) { + VkImageMemoryBarrier b{}; b.sType=VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + b.oldLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + b.newLayout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + b.srcQueueFamilyIndex=b.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED; + b.image=cursorImg; b.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}; + b.srcAccessMask=VK_ACCESS_SHADER_READ_BIT; b.dstAccessMask=VK_ACCESS_TRANSFER_WRITE_BIT; + vk_.CmdPipelineBarrier(cb,VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT, + 0,0,nullptr,0,nullptr,1,&b); + VkBufferImageCopy r{}; r.imageSubresource={VK_IMAGE_ASPECT_COLOR_BIT,0,0,1}; + r.imageExtent={(uint32_t)curW,(uint32_t)curH,1}; + vk_.CmdCopyBufferToImage(cb,cursorUpload,cursorImg,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&r); + b.oldLayout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + b.newLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + b.srcAccessMask=VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask=VK_ACCESS_SHADER_READ_BIT; + postUpload.push_back(b); + } + if (!postUpload.empty()) + vk_.CmdPipelineBarrier(cb,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0,0,nullptr,0,nullptr,(uint32_t)postUpload.size(),postUpload.data()); + + VkRenderPassBeginInfo rpi{}; rpi.sType=VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + rpi.renderPass=renderPass; rpi.framebuffer=swapchainFBs[imgIdx]; rpi.renderArea={{0,0},swapchainExt}; + VkClearValue clr={{{0.f,0.f,0.f,1.f}}}; rpi.clearValueCount=1; rpi.pClearValues=&clr; + + vk_.CmdBeginRenderPass(cb,&rpi,VK_SUBPASS_CONTENTS_INLINE); + VkViewport vp{0,0,(float)swapchainExt.width,(float)swapchainExt.height,0,1}; + vk_.CmdSetViewport(cb,0,1,&vp); + VkRect2D sc{{0,0},swapchainExt}; vk_.CmdSetScissor(cb,0,1,&sc); + + vk_.CmdBindPipeline(cb,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeline); + for (auto& d:draws) { + if (d.ds==VK_NULL_HANDLE) continue; + vk_.CmdBindDescriptorSets(cb,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeLayout,0,1,&d.ds,0,nullptr); + WindowPushConstants pc{}; + pc.ndcX0=(ox+(float)d.x*sx)/cw*2.f-1.f; + pc.ndcY0=(oy+(float)d.y*sy)/ch*2.f-1.f; + pc.ndcX1=(ox+(float)(d.x+d.w)*sx)/cw*2.f-1.f; + pc.ndcY1=(oy+(float)(d.y+d.h)*sy)/ch*2.f-1.f; + pc.effectId = activeEffectId; + pc.sharpness = activeSharpness; + pc.resW = (float)std::max(1, d.w); + pc.resH = (float)std::max(1, d.h); + pc.brightness = activeBrightness; + pc.contrast = activeContrast; + pc.gamma = activeGamma; + vk_.CmdPushConstants(cb,pipeLayout,VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT,0,sizeof(pc),&pc); + vk_.CmdDraw(cb,4,1,0,0); + } + + if (curVis && cursorImg!=VK_NULL_HANDLE && cursorDS!=VK_NULL_HANDLE) { + vk_.CmdBindPipeline(cb,VK_PIPELINE_BIND_POINT_GRAPHICS,cursorPipe); + vk_.CmdBindDescriptorSets(cb,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeLayout,0,1,&cursorDS,0,nullptr); + float cx=(float)std::max(0,(int)ptrX-curHotX), cy=(float)std::max(0,(int)ptrY-curHotY); + WindowPushConstants cpc{}; + cpc.ndcX0=(ox+cx*sx)/cw*2.f-1.f; cpc.ndcY0=(oy+cy*sy)/ch*2.f-1.f; + cpc.ndcX1=(ox+(cx+curW)*sx)/cw*2.f-1.f; cpc.ndcY1=(oy+(cy+curH)*sy)/ch*2.f-1.f; + cpc.effectId = 0; + cpc.sharpness = 0.5f; + cpc.resW = (float)std::max(1, (int)curW); + cpc.resH = (float)std::max(1, (int)curH); + cpc.brightness = 0.0f; + cpc.contrast = 0.0f; + cpc.gamma = 1.0f; + vk_.CmdPushConstants(cb,pipeLayout,VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT,0,sizeof(cpc),&cpc); + vk_.CmdDraw(cb,4,1,0,0); + } + vk_.CmdEndRenderPass(cb); + VkResult endStatus = vk_.EndCommandBuffer(cb); + if (endStatus!=VK_SUCCESS) { + RLOG_E("recordCmdBuf: EndCommandBuffer failed with status=%d (swapRB=%d draws=%zu imgIdx=%u)", + (int)endStatus, (int)swapRB, draws.size(), imgIdx); + throw std::runtime_error("end cb"); + } +} + +void VulkanRendererContext::renderLoop() { + + while (isRunning) { + { std::unique_lock lk(dirtyMutex); + dirtyCV.wait(lk,[this]{ + return !isRunning||(!surfaceDetached.load()&&(needsRender.load()||fbResized.load()))||cursorMoved.load(); }); } + if (!isRunning) break; + + if (swapchain == VK_NULL_HANDLE || cmdBufs.empty()) continue; + try { renderFrame(); } catch(...) {} + } +} + +void VulkanRendererContext::flushDeleteQueue() { + if (deleteQueue.empty()) return; + if (!vk_.GetFenceStatus || vk_.GetFenceStatus(device, inFlightFences[currentFrame]) == VK_NOT_READY) { + vk_.WaitForFences(device,1,&inFlightFences[currentFrame],VK_TRUE,UINT64_MAX); + } + for (auto& wt:deleteQueue) { + if (!wt.isAHB) { + if (wt.ds!=VK_NULL_HANDLE) vk_.FreeDescriptorSets(device,winTexPool,1,&wt.ds); + if (wt.view!=VK_NULL_HANDLE) vk_.DestroyImageView(device,wt.view,nullptr); + if (wt.img!=VK_NULL_HANDLE) vk_.DestroyImage(device,wt.img,nullptr); + if (wt.mem!=VK_NULL_HANDLE) vk_.FreeMemory(device,wt.mem,nullptr); + } + if (wt.stg!=VK_NULL_HANDLE){vk_.DestroyBuffer(device,wt.stg,nullptr);vk_.FreeMemory(device,wt.stgMem,nullptr);} + } + deleteQueue.clear(); +} + +void VulkanRendererContext::renderFrame() { + std::shared_lock frameLock(frameMutex); + + needsRender.store(false,std::memory_order_relaxed); + cursorMoved.store(false,std::memory_order_relaxed); + if (surfaceDetached.load(std::memory_order_acquire)) return; + if (scanoutActive.load()) { + applyScanoutBuffer(); + + if (!scanoutBlackFrameDone.load()) { + scanoutBlackFrameDone.store(true); + + std::lock_guard lk(renderMutex); + renderList.clear(); + } else { + return; + } + } else { + scanoutBlackFrameDone.store(false); + } + if (surfaceWidth==0||surfaceHeight==0) return; + + if (fbResized.load()) { + for (auto& f:inFlightFences) vk_.WaitForFences(device,1,&f,VK_TRUE,UINT64_MAX); + cleanupSwapchain(); + bool ok=false; + try{createSwapchain();createFramebuffers();createCmdBufs();imgInFlight.assign(swapchainImages.size(),VK_NULL_HANDLE); +ok=true;}catch(...){} + if (ok) fbResized.store(false); + return; + } + + flushDeleteQueue(); + if (currentFrame >= cmdBufs.size() || cmdBufs[currentFrame] == VK_NULL_HANDLE) return; + bool currentFenceWaited = false; + if (!vk_.GetFenceStatus || vk_.GetFenceStatus(device, inFlightFences[currentFrame]) == VK_NOT_READY) { + vk_.WaitForFences(device,1,&inFlightFences[currentFrame],VK_TRUE,UINT64_MAX); + currentFenceWaited = true; + } + + uint32_t imgIdx; + VkResult res=vk_.AcquireNextImageKHR(device,swapchain,UINT64_MAX,imgAvailSems[currentFrame],VK_NULL_HANDLE,&imgIdx); + if (res==VK_ERROR_OUT_OF_DATE_KHR||res==VK_ERROR_SURFACE_LOST_KHR){fbResized.store(true);return;} + if (res!=VK_SUCCESS&&res!=VK_SUBOPTIMAL_KHR) return; + if (imgIdx >= swapchainFBs.size() || imgIdx >= swapchainImages.size()) { + RLOG_E("renderFrame: invalid acquired image index=%u (fb=%zu images=%zu)", + imgIdx, swapchainFBs.size(), swapchainImages.size()); + return; + } + + if (imgInFlight.size()!=swapchainImages.size()) imgInFlight.assign(swapchainImages.size(),VK_NULL_HANDLE); + if (imgInFlight[imgIdx]!=VK_NULL_HANDLE && + (!currentFenceWaited || imgInFlight[imgIdx] != inFlightFences[currentFrame])) { + if (!vk_.GetFenceStatus || vk_.GetFenceStatus(device, imgInFlight[imgIdx]) == VK_NOT_READY) { + vk_.WaitForFences(device,1,&imgInFlight[imgIdx],VK_TRUE,UINT64_MAX); + } + } + imgInFlight[imgIdx]=inFlightFences[currentFrame]; + + vk_.ResetCommandBuffer(cmdBufs[currentFrame],0); + + std::vector draws; + draws.reserve(renderList.size()); + float ox,oy,sx,sy,cw,ch; + short ptrX,ptrY,curHotX,curHotY,curW,curH; bool curVis; + VkBuffer curUpload=VK_NULL_HANDLE; bool hasCurUpload=false; + + { + std::lock_guard lk(renderMutex); + ox=sceneOffsetX; oy=sceneOffsetY; sx=sceneScaleX; sy=sceneScaleY; + cw=(float)containerWidth; ch=(float)containerHeight; + uint32_t packedXY = pointerXY.load(std::memory_order_acquire); + ptrX = (short)(int16_t)(packedXY >> 16); + ptrY = (short)(int16_t)(packedXY & 0xFFFF); + curHotX=cursorHotX; curHotY=cursorHotY; curW=cursorTexW; curH=cursorTexH; + curVis=cursorVisible.load(); + + for (auto& re:renderList) { + auto it=texMap.find(re.id); + if (it==texMap.end()) continue; + WinTex& wt=it->second; + if (wt.ds==VK_NULL_HANDLE) continue; + DrawEntry de{wt.img,wt.ds,VK_NULL_HANDLE,re.x,re.y,wt.w,wt.h}; + de.isAHB=wt.isAHB; + if (wt.needsTransition) { de.needsTransition=true; wt.needsTransition=false; } + if (wt.dirty && !wt.isAHB && wt.stg!=VK_NULL_HANDLE) { + de.upload=wt.stg; + wt.dirty=false; + } else if (wt.isAHB && wt.dirty) { + wt.dirty=false; + } + draws.push_back(de); + } + + if (isCursorImageDirty.load() && cursorImg!=VK_NULL_HANDLE && !cursorPixels.empty()) { + VkDeviceSize csz=(VkDeviceSize)cursorTexW*cursorTexH*4; + ensureCursorStaging(csz); + memcpy(cursorStgP,cursorPixels.data(),csz); + isCursorImageDirty.store(false); hasCurUpload=true; curUpload=cursorStg; + } + } + + bool effectiveCurVis = curVis && !scanoutActive.load(); + recordCmdBuf(cmdBufs[currentFrame],imgIdx,draws,curUpload,hasCurUpload, + ox,oy,sx,sy,cw,ch,ptrX,ptrY,curHotX,curHotY,curW,curH,effectiveCurVis); + + VkSemaphore wSem[]={imgAvailSems[currentFrame]}, sSem[]={renderDoneSems[currentFrame]}; + VkPipelineStageFlags wStage[]={VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + VkSubmitInfo si{}; si.sType=VK_STRUCTURE_TYPE_SUBMIT_INFO; + si.waitSemaphoreCount=1; si.pWaitSemaphores=wSem; si.pWaitDstStageMask=wStage; + si.commandBufferCount=1; si.pCommandBuffers=&cmdBufs[currentFrame]; + si.signalSemaphoreCount=1; si.pSignalSemaphores=sSem; + + vk_.ResetFences(device,1,&inFlightFences[currentFrame]); + if (vk_.QueueSubmit(graphicsQueue,1,&si,inFlightFences[currentFrame])!=VK_SUCCESS) { + vk_.DestroyFence(device,inFlightFences[currentFrame],nullptr); + VkFenceCreateInfo fi{}; fi.sType=VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fi.flags=VK_FENCE_CREATE_SIGNALED_BIT; + vk_.CreateFence(device,&fi,nullptr,&inFlightFences[currentFrame]); + return; + } + VkSwapchainKHR scs[]={swapchain}; + VkPresentInfoKHR pi{}; pi.sType=VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + pi.waitSemaphoreCount=1; pi.pWaitSemaphores=sSem; pi.swapchainCount=1; pi.pSwapchains=scs; pi.pImageIndices=&imgIdx; + res=vk_.QueuePresentKHR(graphicsQueue,&pi); + if (res==VK_ERROR_OUT_OF_DATE_KHR||res==VK_ERROR_SURFACE_LOST_KHR) fbResized.store(true); + currentFrame=(currentFrame+1)%MAX_FRAMES_IN_FLIGHT; +} + +void VulkanRendererContext::onSurfaceResized(int w, int h) { + std::lock_guard lk(renderMutex); + if (w==0||h==0) return; + surfaceWidth=w; surfaceHeight=h; fbResized.store(true); dirtyCV.notify_one(); +} + +void VulkanRendererContext::detachSurface() { + surfaceDetached.store(true, std::memory_order_release); + dirtyCV.notify_all(); + + { std::unique_lock frameLock(frameMutex); } + + vk_.DeviceWaitIdle(device); + cleanupSwapchain(); + if (surface != VK_NULL_HANDLE) { + vk_.DestroySurfaceKHR(instance, surface, nullptr); + surface = VK_NULL_HANDLE; + } + if (window) { + ANativeWindow_release(window); + window = nullptr; + } +} + +bool VulkanRendererContext::reattachSurface(ANativeWindow* newWindow) { + if (window) { ANativeWindow_release(window); window = nullptr; } + window = newWindow; + VkAndroidSurfaceCreateInfoKHR ci{}; + ci.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + ci.window = window; + if (vk_.CreateAndroidSurfaceKHR(instance, &ci, nullptr, &surface) != VK_SUCCESS) { + __android_log_print(ANDROID_LOG_ERROR, "Winlator_Renderer", "reattachSurface: CreateAndroidSurface failed"); + ANativeWindow_release(window); window = nullptr; + return false; + } + { + std::unique_lock frameLock(frameMutex); + try { + createSwapchain(); + createFramebuffers(); + createCmdBufs(); + imgInFlight.assign(swapchainImages.size(), VK_NULL_HANDLE); + } catch (...) { + __android_log_print(ANDROID_LOG_ERROR, "Winlator_Renderer", "reattachSurface: swapchain recreate failed"); + return false; + } + surfaceDetached.store(false, std::memory_order_release); + } + needsRender.store(true, std::memory_order_release); + dirtyCV.notify_all(); + __android_log_print(ANDROID_LOG_DEBUG, "Winlator_Renderer", "reattachSurface: OK"); + return true; +} + +void VulkanRendererContext::setTransform(float ox, float oy, float sx, float sy) { + { std::lock_guard lk(renderMutex); sceneOffsetX=ox;sceneOffsetY=oy;sceneScaleX=sx;sceneScaleY=sy; } + needsRender.store(true); dirtyCV.notify_one(); +} +void VulkanRendererContext::updatePointerPosition(short x, short y) { + uint32_t packed = ((uint32_t)(uint16_t)x << 16) | (uint32_t)(uint16_t)y; + pointerXY.store(packed, std::memory_order_release); + if (cursorVisible.load()) { cursorMoved.store(true); dirtyCV.notify_one(); } +} +void VulkanRendererContext::setCursorVisible(bool v) { + cursorVisible.store(v); cursorMoved.store(true); dirtyCV.notify_one(); +} +void VulkanRendererContext::updateCursorImage(void* px, short w, short h, short hotX, short hotY) { + if (!px||w<=0||h<=0) return; + std::lock_guard lk(renderMutex); + ensureCursorTex(w,h); + cursorPixels.resize((size_t)w*h); memcpy(cursorPixels.data(),px,(size_t)w*h*4); + cursorHotX=hotX; cursorHotY=hotY; + isCursorImageDirty.store(true); needsRender.store(true); dirtyCV.notify_one(); +} + +void VulkanRendererContext::updateWindowContent(int64_t id, void* px, short w, short h, short stride, int, int) { + if (!px||w<=0||h<=0) return; + std::lock_guard lk(renderMutex); + WinTex& wt=texMap[id]; + if (wt.img==VK_NULL_HANDLE || wt.w!=w || wt.h!=h) { + // If the slot was previously aliasing an AHB import its resources + // are owned by ahbImportCache; just clear the alias instead of freeing. + if (wt.img!=VK_NULL_HANDLE) { + if (wt.isAHB) wt = {}; + else destroyWinTex(wt); + } + if (!createWinTexResources(wt,w,h)) { texMap.erase(id); return; } + } + if (!wt.mapped) return; + + size_t dstPitch = (size_t)w * 4; + wt.dirty = true; + + const int32_t srcStride = stride > 0 ? stride : w; + uint32_t* src2 = static_cast(px); + uint8_t* dst2 = static_cast(wt.mapped); + const bool stridesMatch = (srcStride == w) && + (dstPitch == (size_t)w * 4); + if (stridesMatch) { + memcpy(dst2, px, (size_t)w * h * 4); + } else { + for (int row = 0; row < h; ++row) { + memcpy(dst2 + (size_t)row * dstPitch, + src2 + (size_t)row * srcStride, + (size_t)w * 4); + } + } + needsRender.store(true); dirtyCV.notify_one(); +} + +void VulkanRendererContext::updateWindowContentAHB(int64_t id, AHardwareBuffer* ahb, short, short, int, int) { + if (!ahb) return; + std::lock_guard lk(renderMutex); + + auto cit = ahbImportCache.find(ahb); + if (cit == ahbImportCache.end()) { + WinTex tmp{}; + if (!importAHBToWinTex(tmp, ahb)) { + RLOG_E("updateWindowContentAHB: import failed for id=%" PRId64, id); + return; + } + AHardwareBuffer_acquire(ahb); + ahbImportCache[ahb] = tmp; + windowAhbs[id].push_back(ahb); + cit = ahbImportCache.find(ahb); + } else { + // Track ownership for this window if it's seeing this AHB for the first time. + auto& list = windowAhbs[id]; + if (std::find(list.begin(), list.end(), ahb) == list.end()) { + list.push_back(ahb); + } + } + + WinTex& src = cit->second; + WinTex& wt = texMap[id]; + + // If the texMap slot was previously a non-AHB CPU texture, free it before + // overwriting (its resources are not owned by ahbImportCache). + if (wt.img != VK_NULL_HANDLE && !wt.isAHB) { + destroyWinTex(wt); + wt = {}; + } + + wt.img = src.img; + wt.mem = src.mem; + wt.view = src.view; + wt.ds = src.ds; + wt.isAHB = true; + wt.ahb = ahb; + wt.w = src.w; + wt.h = src.h; + + // First use of the imported AHB needs an UNDEFINED -> SHADER_READ_ONLY transition. + if (src.needsTransition) { + wt.needsTransition = true; + src.needsTransition = false; + } + needsRender.store(true); dirtyCV.notify_one(); +} + +void VulkanRendererContext::setRenderList(const int64_t* ids, const int* xs, const int* ys, int count) { + std::lock_guard lk(renderMutex); + renderList.resize(count); + for (int i=0;i lk(renderMutex); + + auto it = texMap.find(id); + if (it != texMap.end()) { + // If the slot holds an AHB import its resources belong to ahbImportCache + // and must not be destroyed here — just clear the alias. + if (!it->second.isAHB) destroyWinTex(it->second); + else it->second = {}; + texMap.erase(it); + } + + auto wit = windowAhbs.find(id); + if (wit != windowAhbs.end()) { + for (AHardwareBuffer* ahb : wit->second) { + auto cit = ahbImportCache.find(ahb); + if (cit != ahbImportCache.end()) { + WinTex deferred = cit->second; + // Tag as non-AHB so flushDeleteQueue treats it as a normal resource. + deferred.isAHB = false; + deferred.ahb = nullptr; + deleteQueue.push_back(deferred); + AHardwareBuffer_release(ahb); + ahbImportCache.erase(cit); + } + } + windowAhbs.erase(wit); + } + + renderList.erase(std::remove_if(renderList.begin(),renderList.end(), + [id](const RenderEntry& e){return e.id==id;}),renderList.end()); + needsRender.store(true); dirtyCV.notify_one(); +} + +void VulkanRendererContext::cleanupAllAHBCache() { + for (auto& [ahb, wt] : ahbImportCache) { + if (wt.ds != VK_NULL_HANDLE) vk_.FreeDescriptorSets(device, winTexPool, 1, &wt.ds); + if (wt.view != VK_NULL_HANDLE) vk_.DestroyImageView(device, wt.view, nullptr); + if (wt.img != VK_NULL_HANDLE) vk_.DestroyImage(device, wt.img, nullptr); + if (wt.mem != VK_NULL_HANDLE) vk_.FreeMemory(device, wt.mem, nullptr); + AHardwareBuffer_release(ahb); + } + ahbImportCache.clear(); + windowAhbs.clear(); +} + +#include +#include +#include +#define SCANOUT_LOG(...) __android_log_print(ANDROID_LOG_DEBUG,"Winlator_Scanout",__VA_ARGS__) + +typedef void* (*pfn_SCCreateFromWindow)(ANativeWindow*, const char*); +typedef void (*pfn_SCRelease)(void*); +typedef void* (*pfn_STCreate)(); +typedef void (*pfn_STDelete)(void*); +typedef void (*pfn_STApply)(void*); +typedef void (*pfn_STSetBuffer)(void*, void*, AHardwareBuffer*, int); +typedef void (*pfn_STSetZOrder)(void*, void*, int32_t); +typedef void (*pfn_STSetVisibility)(void*, void*, int8_t); +typedef void (*pfn_STSetGeometry)(void*, void*, const ARect*, const ARect*, int32_t); +typedef void (*pfn_STSetBackPressure)(void*, void*, bool); + +bool VulkanRendererContext::loadScanoutApi() { + if (scanoutApiLoaded) return fnSCCreateFromWin != nullptr; + scanoutApiLoaded = true; + int apiLevel = android_get_device_api_level(); + if (apiLevel < 29) { SCANOUT_LOG("loadScanoutApi: API < 29, unavailable"); return false; } + + void* lib = dlopen("libandroid.so", RTLD_NOW | RTLD_NOLOAD); + if (!lib) lib = dlopen("libandroid.so", RTLD_NOW); + if (!lib) { SCANOUT_LOG("loadScanoutApi: dlopen libandroid.so failed: %s", dlerror()); return false; } + + fnSCCreateFromWin = dlsym(lib, "ASurfaceControl_createFromWindow"); + fnSCRelease = dlsym(lib, "ASurfaceControl_release"); + fnSTCreate = dlsym(lib, "ASurfaceTransaction_create"); + fnSTDelete = dlsym(lib, "ASurfaceTransaction_delete"); + fnSTApply = dlsym(lib, "ASurfaceTransaction_apply"); + fnSTSetBuffer = dlsym(lib, "ASurfaceTransaction_setBuffer"); + fnSTSetZOrder = dlsym(lib, "ASurfaceTransaction_setZOrder"); + fnSTSetVisibility = dlsym(lib, "ASurfaceTransaction_setVisibility"); + fnSTSetGeometry = dlsym(lib, "ASurfaceTransaction_setGeometry"); + fnSTSetBackPressure = dlsym(lib, "ASurfaceTransaction_setEnableBackPressure"); + + bool coreOk = fnSCCreateFromWin && fnSCRelease && + fnSTCreate && fnSTDelete && fnSTApply && + fnSTSetBuffer && fnSTSetVisibility && fnSTSetGeometry; + if (!coreOk) { + SCANOUT_LOG("loadScanoutApi: core symbols missing, scanout disabled"); + fnSCCreateFromWin = fnSCRelease = fnSTCreate = fnSTDelete = fnSTApply = + fnSTSetBuffer = fnSTSetZOrder = fnSTSetVisibility = fnSTSetGeometry = nullptr; + return false; + } + if (!fnSTSetZOrder) SCANOUT_LOG("loadScanoutApi: setZOrder unavailable (non-critical)"); + const char* gpuBlitEnv = std::getenv("WINLATOR_SCANOUT_GPU_BLIT"); + scanoutEnvGpuBlit = (!gpuBlitEnv || gpuBlitEnv[0] == '1'); + scanoutAlwaysGpuBlit = scanoutEnvGpuBlit || swapRB; + SCANOUT_LOG("loadScanoutApi: envGpuBlit=%d swapRB=%d alwaysGpuBlit=%d", + (int)scanoutEnvGpuBlit, (int)swapRB, (int)scanoutAlwaysGpuBlit); + SCANOUT_LOG("loadScanoutApi: core symbols OK, scanout enabled"); + return true; +} + +#define SC_CREATE(win, name) ((pfn_SCCreateFromWindow)fnSCCreateFromWin)((win), (name)) +#define SC_RELEASE(sc) ((pfn_SCRelease)fnSCRelease)((sc)) +#define ST_CREATE() ((pfn_STCreate)fnSTCreate)() +#define ST_DELETE(t) ((pfn_STDelete)fnSTDelete)((t)) +#define ST_APPLY(t) ((pfn_STApply)fnSTApply)((t)) +#define ST_SETBUF(t,sc,b,f) ((pfn_STSetBuffer)fnSTSetBuffer)((t),(sc),(b),(f)) +#define ST_SETZORDER(t,sc,z) if(fnSTSetZOrder) ((pfn_STSetZOrder)fnSTSetZOrder)((t),(sc),(z)) +#define ST_SETVIS(t,sc,v) ((pfn_STSetVisibility)fnSTSetVisibility)((t),(sc),(v)) +#define ST_SETGEO(t,sc,s,d,r) ((pfn_STSetGeometry)fnSTSetGeometry)((t),(sc),(s),(d),(r)) +#define ST_SETBP(t,sc,v) if(fnSTSetBackPressure) ((pfn_STSetBackPressure)fnSTSetBackPressure)((t),(sc),(v)) + +void VulkanRendererContext::initScanout() { + if (scanoutActive.load()) return; + if (!window || !loadScanoutApi()) { + SCANOUT_LOG("initScanout: loadApi failed, aborting"); + return; + } + + scanoutGameSC = SC_CREATE(window, "winlator_game"); + scanoutCursorSC = SC_CREATE(window, "winlator_cursor"); + if (!scanoutGameSC || !scanoutCursorSC) { + SCANOUT_LOG("initScanout: SC creation failed, aborting"); + if (scanoutGameSC) { SC_RELEASE(scanoutGameSC); scanoutGameSC=nullptr; } + if (scanoutCursorSC) { SC_RELEASE(scanoutCursorSC); scanoutCursorSC=nullptr; } + return; + } + + void* t = ST_CREATE(); + ST_SETZORDER(t, scanoutGameSC, 0); + ST_SETZORDER(t, scanoutCursorSC, 1); + ST_SETVIS(t, scanoutGameSC, 0); + ST_SETVIS(t, scanoutCursorSC, 0); + ST_APPLY(t); + ST_DELETE(t); + + gameScVisible = false; lastDstW = 0; + gameFrameDelivered.store(false); + scanoutActive.store(true); + if (scanoutBlitCb == VK_NULL_HANDLE) { + VkCommandBufferAllocateInfo cbi{}; + cbi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + cbi.commandPool = cmdPool; + cbi.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cbi.commandBufferCount = 1; + if (vk_.AllocateCommandBuffers(device, &cbi, &scanoutBlitCb) != VK_SUCCESS) { + scanoutBlitCb = VK_NULL_HANDLE; + } + } + if (scanoutBlitFence == VK_NULL_HANDLE) { + VkFenceCreateInfo fi{}; + fi.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fi.flags = VK_FENCE_CREATE_SIGNALED_BIT; + if (vk_.CreateFence(device, &fi, nullptr, &scanoutBlitFence) != VK_SUCCESS) { + scanoutBlitFence = VK_NULL_HANDLE; + } + } + scanoutAlwaysGpuBlit = scanoutEnvGpuBlit || swapRB; + scanoutNeedsGpuBlit = scanoutAlwaysGpuBlit; + SCANOUT_LOG("initScanout: SUCCESS, scanout active"); +} + +void VulkanRendererContext::destroyScanout() { + if (!scanoutActive.load()) return; + scanoutActive.store(false); + + // Drop any pending buffer + its fence FD that never made it to ST_APPLY. + { std::lock_guard lk(scanoutMutex); + if (scanoutPending.ahb) { + AHardwareBuffer_release(scanoutPending.ahb); + } + if (scanoutPending.fenceFd >= 0) ::close(scanoutPending.fenceFd); + scanoutPending = {}; + scanoutPendingDirty.store(false, std::memory_order_relaxed); + } + + if (scanoutGameSC || scanoutCursorSC) { + void* t = ST_CREATE(); + if (scanoutGameSC) ST_SETVIS(t, scanoutGameSC, 0); + if (scanoutCursorSC) ST_SETVIS(t, scanoutCursorSC, 0); + ST_APPLY(t); + ST_DELETE(t); + } + + if (scanoutGameSC) { SC_RELEASE(scanoutGameSC); scanoutGameSC=nullptr; } + if (scanoutCursorSC) { SC_RELEASE(scanoutCursorSC); scanoutCursorSC=nullptr; } + if (scanoutCursorBuf) { + AHardwareBuffer_release(reinterpret_cast(scanoutCursorBuf)); + scanoutCursorBuf=nullptr; + } + if (scanoutLocalImg != VK_NULL_HANDLE) { + vk_.DestroyImage(device, scanoutLocalImg, nullptr); + scanoutLocalImg = VK_NULL_HANDLE; + } + if (scanoutLocalMem != VK_NULL_HANDLE) { + vk_.FreeMemory(device, scanoutLocalMem, nullptr); + scanoutLocalMem = VK_NULL_HANDLE; + } + if (scanoutLocalAhb) { + AHardwareBuffer_release(scanoutLocalAhb); + scanoutLocalAhb = nullptr; + } + if (scanoutBlitFence != VK_NULL_HANDLE) { + vk_.WaitForFences(device, 1, &scanoutBlitFence, VK_TRUE, UINT64_MAX); + vk_.DestroyFence(device, scanoutBlitFence, nullptr); + scanoutBlitFence = VK_NULL_HANDLE; + } + if (scanoutBlitCb != VK_NULL_HANDLE) { + vk_.FreeCommandBuffers(device, cmdPool, 1, &scanoutBlitCb); + scanoutBlitCb = VK_NULL_HANDLE; + } + scanoutLocalW = scanoutLocalH = 0; + scanoutNeedsGpuBlit = false; + scanoutCursorBufW = scanoutCursorBufH = 0; + + // Reset elision state so the next session re-issues geo and visibility. + scanoutLastSrc = {}; + scanoutLastDst = {}; + scanoutGeoDirty = true; + scanoutVisShown = false; +} + +void VulkanRendererContext::scanoutSetBuffer(AHardwareBuffer* ahb, int x, int y, int w, int h, int fenceFd) { + if (!scanoutActive.load() || !scanoutGameSC || !ahb) { + RLOG("scanoutSetBuffer: SKIPPED active=%d sc=%p ahb=%p", + (int)scanoutActive.load(),scanoutGameSC,(void*)ahb); + if (fenceFd >= 0) ::close(fenceFd); + return; + } + static int _scanoutBufCnt=0; + if (++_scanoutBufCnt == 1) { + AHardwareBuffer_Desc d{}; + AHardwareBuffer_describe(ahb, &d); + bool hasOverlay = (d.usage & AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY) != 0; + RLOG("FIRST FRAME: fmt=%u %ux%u usage=0x%llx COMPOSER_OVERLAY=%s", + d.format, d.width, d.height, (unsigned long long)d.usage, + hasOverlay ? "YES" : "NO"); + scanoutNeedsGpuBlit = !hasOverlay; + } + AHardwareBuffer_acquire(ahb); + { std::lock_guard lk(scanoutMutex); + // Drop any previously-queued buffer that hasn't been applied yet, and + // close its fence FD to avoid a leak. + if (scanoutPendingDirty.load() && scanoutPending.ahb && scanoutPending.ahb != ahb) { + AHardwareBuffer_release(scanoutPending.ahb); + if (scanoutPending.fenceFd >= 0) ::close(scanoutPending.fenceFd); + } + scanoutPending = {ahb, x, y, w, h, fenceFd}; + scanoutPendingDirty.store(true, std::memory_order_release); } + needsRender.store(true, std::memory_order_release); + dirtyCV.notify_one(); +} + +void VulkanRendererContext::initScanoutFromWindows(ANativeWindow* gameWin, ANativeWindow* cursorWin) { + if (scanoutActive.load()) destroyScanout(); + if (!loadScanoutApi()) { + ANativeWindow_release(gameWin); ANativeWindow_release(cursorWin); + initScanout(); return; + } + scanoutGameSC = SC_CREATE(gameWin, "winlator_game_buf"); + scanoutCursorSC = SC_CREATE(cursorWin, "winlator_cursor_buf"); + ANativeWindow_release(gameWin); ANativeWindow_release(cursorWin); + if (!scanoutGameSC || !scanoutCursorSC) { + SCANOUT_LOG("initScanoutFromWindows: SC creation failed, fallback to child path"); + if (scanoutGameSC) { SC_RELEASE(scanoutGameSC); scanoutGameSC=nullptr; } + if (scanoutCursorSC) { SC_RELEASE(scanoutCursorSC); scanoutCursorSC=nullptr; } + initScanout(); return; + } + void* t = ST_CREATE(); + ST_SETZORDER(t, scanoutGameSC, 0); + ST_SETZORDER(t, scanoutCursorSC, 1); + ST_SETVIS(t, scanoutGameSC, 0); + ST_SETVIS(t, scanoutCursorSC, 0); + ST_APPLY(t); ST_DELETE(t); + gameScVisible = false; lastDstW = 0; + gameFrameDelivered.store(false); + scanoutActive.store(true); + if (scanoutBlitCb == VK_NULL_HANDLE) { + VkCommandBufferAllocateInfo cbi{}; + cbi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + cbi.commandPool = cmdPool; + cbi.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cbi.commandBufferCount = 1; + if (vk_.AllocateCommandBuffers(device, &cbi, &scanoutBlitCb) != VK_SUCCESS) { + scanoutBlitCb = VK_NULL_HANDLE; + } + } + if (scanoutBlitFence == VK_NULL_HANDLE) { + VkFenceCreateInfo fi{}; + fi.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fi.flags = VK_FENCE_CREATE_SIGNALED_BIT; + if (vk_.CreateFence(device, &fi, nullptr, &scanoutBlitFence) != VK_SUCCESS) { + scanoutBlitFence = VK_NULL_HANDLE; + } + } + scanoutAlwaysGpuBlit = scanoutEnvGpuBlit || swapRB; + scanoutNeedsGpuBlit = scanoutAlwaysGpuBlit; + SCANOUT_LOG("initScanoutFromWindows: SUCCESS (sibling path via Java getSurfaceControl)"); +} + +bool VulkanRendererContext::ensureScanoutLocalAhb(int w, int h, uint32_t ahbFormat) { + if (scanoutLocalAhb && scanoutLocalImg != VK_NULL_HANDLE && + scanoutLocalW == w && scanoutLocalH == h) { + return true; + } + + if (scanoutLocalImg != VK_NULL_HANDLE) { + vk_.DeviceWaitIdle(device); + vk_.DestroyImage(device, scanoutLocalImg, nullptr); + scanoutLocalImg = VK_NULL_HANDLE; + } + if (scanoutLocalMem != VK_NULL_HANDLE) { + vk_.FreeMemory(device, scanoutLocalMem, nullptr); + scanoutLocalMem = VK_NULL_HANDLE; + } + if (scanoutLocalAhb) { + AHardwareBuffer_release(scanoutLocalAhb); + scanoutLocalAhb = nullptr; + } + + AHardwareBuffer_Desc d{}; + d.width = (uint32_t)w; + d.height = (uint32_t)h; + d.layers = 1; + d.format = ahbFormat; + d.usage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER + | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE + | AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY; + if (AHardwareBuffer_allocate(&d, &scanoutLocalAhb) != 0) return false; + + VkAndroidHardwareBufferFormatPropertiesANDROID fmtP{}; + fmtP.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID; + VkAndroidHardwareBufferPropertiesANDROID props{}; + props.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID; + props.pNext = &fmtP; + if (vk_.GetAndroidHardwareBufferPropertiesANDROID(device, scanoutLocalAhb, &props) != VK_SUCCESS) { + AHardwareBuffer_release(scanoutLocalAhb); + scanoutLocalAhb = nullptr; + return false; + } + + bool extFmt = (fmtP.format == VK_FORMAT_UNDEFINED); + VkExternalFormatANDROID ef{}; + ef.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID; + ef.externalFormat = extFmt ? fmtP.externalFormat : 0; + VkExternalMemoryImageCreateInfo emi{}; + emi.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; + emi.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID; + if (extFmt) { + ef.pNext = const_cast(emi.pNext); + emi.pNext = &ef; + } + + VkImageCreateInfo ii{}; + ii.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + ii.pNext = &emi; + ii.imageType = VK_IMAGE_TYPE_2D; + ii.format = extFmt ? VK_FORMAT_UNDEFINED : fmtP.format; + ii.extent = {(uint32_t)w, (uint32_t)h, 1}; + ii.mipLevels = 1; + ii.arrayLayers = 1; + ii.samples = VK_SAMPLE_COUNT_1_BIT; + ii.tiling = VK_IMAGE_TILING_OPTIMAL; + ii.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + ii.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ii.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if (vk_.CreateImage(device, &ii, nullptr, &scanoutLocalImg) != VK_SUCCESS) { + AHardwareBuffer_release(scanoutLocalAhb); + scanoutLocalAhb = nullptr; + return false; + } + + VkImportAndroidHardwareBufferInfoANDROID imp{}; + imp.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID; + imp.buffer = scanoutLocalAhb; + VkMemoryDedicatedAllocateInfo ded{}; + ded.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; + ded.pNext = &imp; + ded.image = scanoutLocalImg; + VkMemoryAllocateInfo mai{}; + mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + mai.pNext = &ded; + mai.allocationSize = props.allocationSize; + mai.memoryTypeIndex = findMemType(props.memoryTypeBits, 0); + if (vk_.AllocateMemory(device, &mai, nullptr, &scanoutLocalMem) != VK_SUCCESS || + vk_.BindImageMemory(device, scanoutLocalImg, scanoutLocalMem, 0) != VK_SUCCESS) { + if (scanoutLocalMem != VK_NULL_HANDLE) { + vk_.FreeMemory(device, scanoutLocalMem, nullptr); + scanoutLocalMem = VK_NULL_HANDLE; + } + vk_.DestroyImage(device, scanoutLocalImg, nullptr); + scanoutLocalImg = VK_NULL_HANDLE; + AHardwareBuffer_release(scanoutLocalAhb); + scanoutLocalAhb = nullptr; + return false; + } + + scanoutLocalW = w; + scanoutLocalH = h; + return true; +} + +void VulkanRendererContext::applyScanoutBuffer() { + if (!scanoutPendingDirty.load(std::memory_order_acquire)) return; + ScanoutPending p; + { std::lock_guard lk(scanoutMutex); + if (!scanoutPendingDirty.load()) return; + p = scanoutPending; + scanoutPendingDirty.store(false, std::memory_order_relaxed); } + AHardwareBuffer* ahb=p.ahb; int w=p.w, h=p.h; (void)p.x; (void)p.y; + int presentFenceFd = p.fenceFd; + if (!ahb || !scanoutGameSC) { + if (presentFenceFd >= 0) ::close(presentFenceFd); + if (ahb) AHardwareBuffer_release(ahb); + return; + } + + int32_t cw = containerWidth > 0 ? containerWidth : w; + int32_t ch = containerHeight > 0 ? containerHeight : h; + ARect src{0, 0, cw, ch}; + ARect dst; + if (scanoutDstW > 0 && scanoutDstH > 0) { + dst = {scanoutDstX, scanoutDstY, scanoutDstX+scanoutDstW, scanoutDstY+scanoutDstH}; + } else { + dst = {0, 0, cw, ch}; + } + + AHardwareBuffer* presentAhb = ahb; + if (scanoutAlwaysGpuBlit || scanoutNeedsGpuBlit) { + AHardwareBuffer_Desc srcDesc{}; + AHardwareBuffer_describe(ahb, &srcDesc); + if (ensureScanoutLocalAhb((int)srcDesc.width, (int)srcDesc.height, srcDesc.format)) { + VkImage srcImg = VK_NULL_HANDLE; + auto it = ahbImportCache.find(ahb); + if (it != ahbImportCache.end()) { + srcImg = it->second.img; + } else { + WinTex tmp{}; + if (importAHBToWinTex(tmp, ahb)) { + AHardwareBuffer_acquire(ahb); + ahbImportCache[ahb] = tmp; + srcImg = tmp.img; + } + } + + if (srcImg != VK_NULL_HANDLE && scanoutLocalImg != VK_NULL_HANDLE && + scanoutBlitCb != VK_NULL_HANDLE && scanoutBlitFence != VK_NULL_HANDLE) { + vk_.WaitForFences(device, 1, &scanoutBlitFence, VK_TRUE, UINT64_MAX); + vk_.ResetFences(device, 1, &scanoutBlitFence); + vk_.ResetCommandBuffer(scanoutBlitCb, 0); + + VkCommandBufferBeginInfo bi{}; + bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vk_.BeginCommandBuffer(scanoutBlitCb, &bi); + + transition(scanoutBlitCb, srcImg, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + 0, VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + transition(scanoutBlitCb, scanoutLocalImg, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkImageCopy region{}; + region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + region.extent = {(uint32_t)w, (uint32_t)h, 1}; + vk_.CmdCopyImage(scanoutBlitCb, + srcImg, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + scanoutLocalImg, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ®ion); + vk_.EndCommandBuffer(scanoutBlitCb); + + VkSubmitInfo si{}; + si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + si.commandBufferCount = 1; + si.pCommandBuffers = &scanoutBlitCb; + vk_.QueueSubmit(graphicsQueue, 1, &si, scanoutBlitFence); + presentAhb = scanoutLocalAhb; + } + } + // GPU blit path: the source AHB is consumed via Vulkan, we don't need + // SurfaceFlinger to wait on the producer fence — close it ourselves. + if (presentFenceFd >= 0) { ::close(presentFenceFd); presentFenceFd = -1; } + } + + void* t=ST_CREATE(); + // ASurfaceTransaction_setBuffer takes ownership of the fence FD on success. + ST_SETBUF(t,scanoutGameSC,presentAhb,presentFenceFd); + + // Elide ST_SETGEO if geometry hasn't changed since the last apply. + auto arectEq = [](const ARect& a, const ARect& b) { + return a.left==b.left && a.top==b.top && a.right==b.right && a.bottom==b.bottom; + }; + if (scanoutGeoDirty || !arectEq(src, scanoutLastSrc) || !arectEq(dst, scanoutLastDst)) { + ST_SETGEO(t, scanoutGameSC, &src, &dst, 0); + scanoutLastSrc = src; + scanoutLastDst = dst; + scanoutGeoDirty = false; + } + if (!scanoutVisShown) { + ST_SETVIS(t, scanoutGameSC, 1); + scanoutVisShown = true; + } + ST_SETBP(t,scanoutGameSC,false); + ST_APPLY(t); + gameFrameDelivered.store(true); + ST_DELETE(t); + AHardwareBuffer_release(ahb); +} + +void VulkanRendererContext::scanoutSetDst(int x, int y, int w, int h) { + if (scanoutDstX==x && scanoutDstY==y && scanoutDstW==w && scanoutDstH==h) return; + scanoutDstX=x; scanoutDstY=y; scanoutDstW=w; scanoutDstH=h; + scanoutGeoDirty = true; +} + +void VulkanRendererContext::scanoutSetCursorImage(void* pixels, short w, short h, short stride) { + if (!scanoutActive.load() || !scanoutCursorSC || !pixels || w<=0 || h<=0) return; + if (stride <= 0) stride = w; + + auto* curBuf = reinterpret_cast(&scanoutCursorBuf); + if (*curBuf && (scanoutCursorBufW != w || scanoutCursorBufH != h)) { + AHardwareBuffer_release(*curBuf); + *curBuf = nullptr; + } + if (!*curBuf) { + AHardwareBuffer_Desc d{}; + d.width = (uint32_t)w; d.height = (uint32_t)h; d.layers = 1; + d.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + d.usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | + AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY; + if (AHardwareBuffer_allocate(&d, curBuf) != 0) return; + scanoutCursorBufW = w; scanoutCursorBufH = h; + } + + AHardwareBuffer_Desc dstDesc{}; + AHardwareBuffer_describe(*curBuf, &dstDesc); + uint32_t dstStride = dstDesc.stride; + + void* dst = nullptr; + if (AHardwareBuffer_lock(*curBuf, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, + -1, nullptr, &dst) != 0) return; + const uint32_t* src = reinterpret_cast(pixels); + auto* dstPx = reinterpret_cast(dst); + for (int row = 0; row < h; ++row) { + memcpy(dstPx + (size_t)row * dstStride, + src + (size_t)row * (uint32_t)stride, + (size_t)w * 4); + } + AHardwareBuffer_unlock(*curBuf, nullptr); + + void* t = ST_CREATE(); + ST_SETBUF(t, scanoutCursorSC, *curBuf, -1); + ST_APPLY(t); + ST_DELETE(t); +} + +void VulkanRendererContext::scanoutSetCursorPos(short x, short y, short hotX, short hotY) { + if (!scanoutActive.load() || !scanoutCursorSC) return; + if (scanoutCursorBufW <= 0 || scanoutCursorBufH <= 0) return; + + int32_t cw = containerWidth > 0 ? containerWidth : surfaceWidth; + int32_t ch = containerHeight > 0 ? containerHeight : surfaceHeight; + int32_t dx = scanoutDstW > 0 ? scanoutDstX : 0; + int32_t dy = scanoutDstW > 0 ? scanoutDstY : 0; + int32_t dw = scanoutDstW > 0 ? scanoutDstW : cw; + int32_t dh = scanoutDstW > 0 ? scanoutDstH : ch; + + float fx = dx + ((float)x / cw) * dw; + float fy = dy + ((float)y / ch) * dh; + float scaleW = (float)dw / cw; + float scaleH = (float)dh / ch; + int32_t curW = (int32_t)(scanoutCursorBufW * scaleW); + int32_t curH = (int32_t)(scanoutCursorBufH * scaleH); + int32_t cx = std::max(0, (int32_t)(fx - hotX * scaleW)); + int32_t cy = std::max(0, (int32_t)(fy - hotY * scaleH)); + + ARect src{0, 0, scanoutCursorBufW, scanoutCursorBufH}; + ARect dst{cx, cy, cx + curW, cy + curH}; + + void* t = ST_CREATE(); + ST_SETGEO(t, scanoutCursorSC, &src, &dst, 0); + ST_SETVIS(t, scanoutCursorSC, 1); + ST_APPLY(t); + ST_DELETE(t); +} + +void VulkanRendererContext::dumpRendererInfo() { + VkPhysicalDeviceProperties props{}; + vk_.GetPhysicalDeviceProperties(physicalDevice,&props); + __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG, + "=== RENDERER INFO ==="); + __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG, + "GPU: %s vendorID=0x%x driverVersion=0x%x apiVersion=%d.%d.%d", + props.deviceName,props.vendorID,props.driverVersion, + VK_VERSION_MAJOR(props.apiVersion),VK_VERSION_MINOR(props.apiVersion),VK_VERSION_PATCH(props.apiVersion)); + __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG, + "Swapchain: %dx%d fmt=%d",swapchainExt.width,swapchainExt.height,(int)swapchainFmt); + std::string pmList; + for(auto pm:availablePresentModes) pmList+=std::to_string((int)pm)+" "; + __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG, + "SupportedPresentModes: [%s] current=%d",pmList.c_str(),(int)requestedPresentMode); + __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG, + "Filter: mode=%d (%s)", filterMode, filterMode==2?(cubicSupported?"CUBIC":"LINEAR"):filterMode==1?"NEAREST":"LINEAR"); + __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG, + "Scanout: active=%d gameFrameDelivered=%d scanoutGameSC=%p", + (int)scanoutActive.load(),(int)gameFrameDelivered.load(),scanoutGameSC); + __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG, + "Surface: %dx%d container: %dx%d", + surfaceWidth,surfaceHeight,containerWidth,containerHeight); + __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG,"=== END RENDERER INFO ==="); +} + +void VulkanRendererContext::setFilterMode(int mode) { + RLOG("setFilterMode: %d -> %d (%s->%s)", filterMode, mode, + filterMode==2?(cubicSupported?"CUBIC":"LINEAR"):filterMode==1?"NEAREST":"LINEAR", mode==2?(cubicSupported?"CUBIC":"LINEAR"):mode==1?"NEAREST":"LINEAR"); + if (filterMode==mode) { RLOG("setFilterMode: already set, skipping"); return; } + filterMode=mode; + vk_.DeviceWaitIdle(device); + if (sampler!=VK_NULL_HANDLE){vk_.DestroySampler(device,sampler,nullptr);sampler=VK_NULL_HANDLE;} + createSampler(); + auto updateDS=[&](VkDescriptorSet ds, VkImageView view){ + if(ds==VK_NULL_HANDLE||view==VK_NULL_HANDLE) return; + VkDescriptorImageInfo dii{}; dii.imageLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + dii.imageView=view; dii.sampler=sampler; + VkWriteDescriptorSet wr{}; wr.sType=VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + wr.dstSet=ds; wr.dstBinding=0; wr.descriptorType=VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + wr.descriptorCount=1; wr.pImageInfo=&dii; + vk_.UpdateDescriptorSets(device,1,&wr,0,nullptr); + }; + int dsCount=0; + for (auto& [id,wt]:texMap) { updateDS(wt.ds, wt.view); if(wt.ds!=VK_NULL_HANDLE) dsCount++; } + for (auto& [ahb,wt]:ahbImportCache) { updateDS(wt.ds, wt.view); if(wt.ds!=VK_NULL_HANDLE) dsCount++; } + if (cursorDS!=VK_NULL_HANDLE&&cursorView!=VK_NULL_HANDLE) { updateDS(cursorDS, cursorView); dsCount++; } + needsRender.store(true); dirtyCV.notify_one(); +} + +void VulkanRendererContext::setSwapRB(bool enabled) { + if (swapRB == enabled) return; + swapRB = enabled; + scanoutAlwaysGpuBlit = scanoutEnvGpuBlit || swapRB; + if (scanoutAlwaysGpuBlit) scanoutNeedsGpuBlit = true; + RLOG("setSwapRB: %d (alwaysGpuBlit=%d)", (int)swapRB, (int)scanoutAlwaysGpuBlit); + if (scanoutActive.load()) { + SCANOUT_LOG("setSwapRB: native swapRB requires GPU blit path (alwaysGpuBlit=%d)", + (int)scanoutAlwaysGpuBlit); + } + needsRender.store(true); + dirtyCV.notify_one(); +} + +void VulkanRendererContext::setEffect(int effectId, float sharpness) { + activeEffectId = effectId; + activeSharpness = std::max(0.0f, std::min(1.0f, sharpness)); + RLOG("setEffect: id=%d sharpness=%.3f", activeEffectId, activeSharpness); + needsRender.store(true); + dirtyCV.notify_one(); +} + +void VulkanRendererContext::setColorAdjustment(float brightness, float contrast, float gamma) { + activeBrightness = brightness; + activeContrast = contrast; + activeGamma = std::max(0.01f, gamma); + needsRender.store(true); + dirtyCV.notify_one(); +} + +void VulkanRendererContext::setPresentMode(VkPresentModeKHR mode) { + bool supported = false; + for (auto pm : availablePresentModes) if (pm == mode) { supported = true; break; } + VkPresentModeKHR target = supported ? mode : VK_PRESENT_MODE_FIFO_KHR; + RLOG("setPresentMode: requested=%d supported=%d -> applying=%d", + (int)mode, (int)supported, (int)target); + if (requestedPresentMode==target) { RLOG("setPresentMode: already set, skipping"); return; } + requestedPresentMode=target; + fbResized.store(true); dirtyCV.notify_one(); +} + +#pragma GCC diagnostic pop diff --git a/app/src/main/cpp/winlator/VulkanRendererContext.h b/app/src/main/cpp/winlator/VulkanRendererContext.h new file mode 100644 index 0000000000..25e3c19028 --- /dev/null +++ b/app/src/main/cpp/winlator/VulkanRendererContext.h @@ -0,0 +1,398 @@ +#pragma once +#include + +// VK_API_VERSION_1_3 was added in Vulkan SDK 1.2.175. +// NDK 22 ships with older headers, so we provide a compat shim. +#ifndef VK_API_VERSION_1_3 +#define VK_API_VERSION_1_3 VK_MAKE_VERSION(1, 3, 0) +#endif +#include +struct VkTable { + + PFN_vkCreateInstance CreateInstance; + + PFN_vkDestroyInstance DestroyInstance; + PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices; + PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceMemoryProperties GetPhysicalDeviceMemoryProperties; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR GetPhysicalDeviceSurfaceFormatsKHR; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR GetPhysicalDeviceSurfacePresentModesKHR; + PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR GetPhysicalDeviceSurfaceSupportKHR; + PFN_vkCreateDevice CreateDevice; + PFN_vkDestroySurfaceKHR DestroySurfaceKHR; + PFN_vkCreateAndroidSurfaceKHR CreateAndroidSurfaceKHR; + + PFN_vkGetDeviceProcAddr GetDeviceProcAddr; + PFN_vkDestroyDevice DestroyDevice; + PFN_vkGetDeviceQueue GetDeviceQueue; + PFN_vkDeviceWaitIdle DeviceWaitIdle; + PFN_vkCreateSwapchainKHR CreateSwapchainKHR; + PFN_vkDestroySwapchainKHR DestroySwapchainKHR; + PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR; + PFN_vkAcquireNextImageKHR AcquireNextImageKHR; + PFN_vkQueuePresentKHR QueuePresentKHR; + PFN_vkQueueSubmit QueueSubmit; + PFN_vkCreateRenderPass CreateRenderPass; + PFN_vkDestroyRenderPass DestroyRenderPass; + PFN_vkCreateFramebuffer CreateFramebuffer; + PFN_vkDestroyFramebuffer DestroyFramebuffer; + PFN_vkCreateImageView CreateImageView; + PFN_vkDestroyImageView DestroyImageView; + PFN_vkCreateImage CreateImage; + PFN_vkDestroyImage DestroyImage; + PFN_vkCreateBuffer CreateBuffer; + PFN_vkDestroyBuffer DestroyBuffer; + PFN_vkAllocateMemory AllocateMemory; + PFN_vkFreeMemory FreeMemory; + PFN_vkMapMemory MapMemory; + PFN_vkFlushMappedMemoryRanges FlushMappedMemoryRanges; + PFN_vkBindBufferMemory BindBufferMemory; + PFN_vkBindImageMemory BindImageMemory; + PFN_vkGetBufferMemoryRequirements GetBufferMemoryRequirements; + PFN_vkGetImageMemoryRequirements GetImageMemoryRequirements; + PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout; + PFN_vkDestroyDescriptorSetLayout DestroyDescriptorSetLayout; + PFN_vkCreateDescriptorPool CreateDescriptorPool; + PFN_vkDestroyDescriptorPool DestroyDescriptorPool; + PFN_vkAllocateDescriptorSets AllocateDescriptorSets; + PFN_vkFreeDescriptorSets FreeDescriptorSets; + PFN_vkUpdateDescriptorSets UpdateDescriptorSets; + PFN_vkCreatePipelineLayout CreatePipelineLayout; + PFN_vkDestroyPipelineLayout DestroyPipelineLayout; + PFN_vkCreateShaderModule CreateShaderModule; + PFN_vkDestroyShaderModule DestroyShaderModule; + PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines; + PFN_vkDestroyPipeline DestroyPipeline; + PFN_vkCreateCommandPool CreateCommandPool; + PFN_vkDestroyCommandPool DestroyCommandPool; + PFN_vkAllocateCommandBuffers AllocateCommandBuffers; + PFN_vkFreeCommandBuffers FreeCommandBuffers; + PFN_vkBeginCommandBuffer BeginCommandBuffer; + PFN_vkEndCommandBuffer EndCommandBuffer; + PFN_vkResetCommandBuffer ResetCommandBuffer; + PFN_vkCmdBeginRenderPass CmdBeginRenderPass; + PFN_vkCmdEndRenderPass CmdEndRenderPass; + PFN_vkCmdBindPipeline CmdBindPipeline; + PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets; + PFN_vkCmdDraw CmdDraw; + PFN_vkCmdPushConstants CmdPushConstants; + PFN_vkCmdSetViewport CmdSetViewport; + PFN_vkCmdSetScissor CmdSetScissor; + PFN_vkCmdPipelineBarrier CmdPipelineBarrier; + PFN_vkCmdCopyImage CmdCopyImage; + PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage; + PFN_vkCreateSampler CreateSampler; + PFN_vkDestroySampler DestroySampler; + PFN_vkCreateSemaphore CreateSemaphore; + PFN_vkDestroySemaphore DestroySemaphore; + PFN_vkCreateFence CreateFence; + PFN_vkDestroyFence DestroyFence; + PFN_vkWaitForFences WaitForFences; + PFN_vkResetFences ResetFences; + PFN_vkGetFenceStatus GetFenceStatus; + + PFN_vkGetAndroidHardwareBufferPropertiesANDROID GetAndroidHardwareBufferPropertiesANDROID; +}; + +#include +#include +#define WLOG_TAG "Winlator_Renderer" +#define RLOG(...) if(verboseLog) __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG,__VA_ARGS__) +#define RLOG_E(...) __android_log_print(ANDROID_LOG_ERROR,WLOG_TAG,__VA_ARGS__) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2; + +struct WindowPushConstants { + float ndcX0, ndcY0, ndcX1, ndcY1; + int effectId; + float sharpness; + float resW; + float resH; + float brightness; + float contrast; + float gamma; +}; + +class VulkanRendererContext { +public: + VulkanRendererContext(ANativeWindow* window, int cWidth, int cHeight, void* adrenotoolsHandle = nullptr); + ~VulkanRendererContext(); + + void onSurfaceResized(int width, int height); + void setTransform(float ox, float oy, float sx, float sy); + void updatePointerPosition(short x, short y); + void updateWindowContent(int64_t id, void* pixels, short w, short h, short stride, int x, int y); + void updateWindowContentAHB(int64_t id, AHardwareBuffer* ahb, short w, short h, int x, int y); + void updateCursorImage(void* pixels, short w, short h, short hotX, short hotY); + void setCursorVisible(bool visible); + void setRenderList(const int64_t* ids, const int* xs, const int* ys, int count); + void removeWindow(int64_t id); + void clearBackbuffer() {} + void beginBatch() {} + void endBatch() {} + void initScanout(); + void destroyScanout(); + void applyScanoutBuffer(); + void initScanoutFromWindows(ANativeWindow* gameWin, ANativeWindow* cursorWin); + void scanoutSetDst(int x, int y, int w, int h); + void scanoutSetBuffer(AHardwareBuffer* ahb, int x, int y, int w, int h, int fenceFd = -1); + void scanoutSetCursorImage(void* pixels, short w, short h, short stride); + void scanoutSetCursorPos(short x, short y, short hotX, short hotY); + std::atomic scanoutActive{false}; + std::atomic gameFrameDelivered{false}; + std::atomic surfaceDetached{false}; + + void detachSurface(); + bool reattachSurface(ANativeWindow* newWindow); + + bool verboseLog = true; + void setVerboseLog(bool v) { verboseLog = v; } + void dumpRendererInfo(); + + std::string adrenoDriverPath; + std::string adrenoDriverName; + std::string adrenoNativeLibDir; + void* vulkanHandle = nullptr; + std::atomic scanoutBlackFrameDone{false}; + PFN_vkGetInstanceProcAddr gipa = nullptr; + VkTable vk_ = {}; + void loadCustomDriver(); + void loadInstanceDispatch(); + void loadDeviceDispatch(); + + void setFilterMode(int mode); + void setSwapRB(bool enabled); + void setEffect(int effectId, float sharpness); + void setColorAdjustment(float brightness, float contrast, float gamma); + void setPresentMode(VkPresentModeKHR mode); + +private: + struct WinTex { + VkImage img = VK_NULL_HANDLE; + VkDeviceMemory mem = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + VkDescriptorSet ds = VK_NULL_HANDLE; + VkBuffer stg = VK_NULL_HANDLE; + VkDeviceMemory stgMem = VK_NULL_HANDLE; + void* mapped = nullptr; + VkDeviceSize cap = 0; + int w = 0; + int h = 0; + bool dirty = false; + bool isAHB = false; + bool needsTransition = false; + AHardwareBuffer* ahb = nullptr; + }; + struct RenderEntry { int64_t id; int x, y; }; + struct DrawEntry { + VkImage img = VK_NULL_HANDLE; + VkDescriptorSet ds = VK_NULL_HANDLE; + VkBuffer upload = VK_NULL_HANDLE; + int x=0, y=0, w=0, h=0; + bool needsTransition = false; + bool isAHB = false; + }; + + ANativeWindow* window; + int surfaceWidth, surfaceHeight, containerWidth, containerHeight; + void* adrenotoolsHandle = nullptr; + int filterMode = 0; + bool swapRB = false; + int activeEffectId = 0; + float activeSharpness = 1.0f; + float activeBrightness = 0.0f; + float activeContrast = 0.0f; + float activeGamma = 1.0f; + float maxAnisotropy = 1.0f; + bool cubicSupported = false; + VkPhysicalDeviceMemoryProperties memProperties{}; + VkPresentModeKHR requestedPresentMode = VK_PRESENT_MODE_FIFO_KHR; + uint32_t graphicsQueueFamilyIndex = 0; + std::vector availablePresentModes; + + std::unordered_map texMap; + + // AHB import cache: re-use a single WinTex per AHardwareBuffer so multiple + // updates that target the same backing buffer share one VkImage + descriptor. + // Ownership is tracked per-window via windowAhbs so removeWindow can drop + // any AHBs it acquired without scanning the whole cache. + std::unordered_map ahbImportCache; + std::unordered_map> windowAhbs; + + std::vector deleteQueue; + std::vector renderList; + + // Scratch barrier vectors used by recordCmdBuf. Held as members so they + // are not re-allocated on the heap every frame. + std::vector frameAhbTransitions; + std::vector framePreUpload; + std::vector framePostUpload; + + void* scanoutGameSC = nullptr; + void* scanoutCursorSC = nullptr; + void* scanoutCursorBuf = nullptr; + int32_t scanoutCursorBufW = 0; + int32_t scanoutCursorBufH = 0; + AHardwareBuffer* scanoutLocalAhb = nullptr; + VkImage scanoutLocalImg = VK_NULL_HANDLE; + VkDeviceMemory scanoutLocalMem = VK_NULL_HANDLE; + int scanoutLocalW = 0; + int scanoutLocalH = 0; + bool scanoutNeedsGpuBlit = false; + bool scanoutApiLoaded = false; + bool scanoutEnvGpuBlit = false; + bool scanoutAlwaysGpuBlit = false; + void* fnSCCreateFromWin = nullptr; + void* fnSCRelease = nullptr; + void* fnSTCreate = nullptr; + void* fnSTDelete = nullptr; + void* fnSTApply = nullptr; + void* fnSTSetBuffer = nullptr; + void* fnSTSetZOrder = nullptr; + void* fnSTSetVisibility = nullptr; + void* fnSTSetGeometry = nullptr; + void* fnSTSetBackPressure = nullptr; + bool loadScanoutApi(); + + int32_t scanoutDstX=0, scanoutDstY=0, scanoutDstW=0, scanoutDstH=0; + + int32_t lastDstX=0, lastDstY=0, lastDstW=0, lastDstH=0; + bool gameScVisible = false; + + // Skip redundant ST_SETGEO / ST_SETVIS calls when nothing has changed. + ARect scanoutLastSrc{}; + ARect scanoutLastDst{}; + bool scanoutGeoDirty = true; + bool scanoutVisShown = false; + + struct ScanoutPending { AHardwareBuffer* ahb=nullptr; int x=0,y=0,w=0,h=0; int fenceFd=-1; }; + std::mutex scanoutMutex; + ScanoutPending scanoutPending{}; + std::atomic scanoutPendingDirty{false}; + + // Packed (x << 16) | (y & 0xFFFF) so both coordinates are loaded together + // and the render thread cannot pick up a mismatched (newX, oldY) pair. + std::atomic pointerXY{0}; + float sceneOffsetX=0.f, sceneOffsetY=0.f, sceneScaleX=1.f, sceneScaleY=1.f; + + std::atomic cursorVisible{false}; + short cursorHotX=0, cursorHotY=0, cursorTexW=0, cursorTexH=0; + std::vector cursorPixels; + std::atomic isCursorImageDirty{false}; + std::atomic cursorMoved{false}; + + VkImage cursorImg = VK_NULL_HANDLE; + VkDeviceMemory cursorMem = VK_NULL_HANDLE; + VkImageView cursorView = VK_NULL_HANDLE; + VkDescriptorPool cursorPool = VK_NULL_HANDLE; + VkDescriptorSet cursorDS = VK_NULL_HANDLE; + VkPipeline cursorPipe = VK_NULL_HANDLE; + VkBuffer cursorStg = VK_NULL_HANDLE; + VkDeviceMemory cursorStgM = VK_NULL_HANDLE; + void* cursorStgP = nullptr; + VkDeviceSize cursorStgC = 0; + + VkInstance instance; + VkSurfaceKHR surface; + VkPhysicalDevice physicalDevice; + VkDevice device; + VkQueue graphicsQueue; + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + VkFormat swapchainFmt; + VkExtent2D swapchainExt; + + std::vector swapchainImages; + std::vector swapchainViews; + std::vector swapchainFBs; + + VkRenderPass renderPass = VK_NULL_HANDLE; + VkDescriptorSetLayout dsLayout = VK_NULL_HANDLE; + VkPipelineLayout pipeLayout = VK_NULL_HANDLE; + + VkPipeline pipeline = VK_NULL_HANDLE; + + VkCommandPool cmdPool = VK_NULL_HANDLE; + std::vector cmdBufs; + VkFence oneTimeFence = VK_NULL_HANDLE; + VkCommandBuffer scanoutBlitCb = VK_NULL_HANDLE; + VkFence scanoutBlitFence = VK_NULL_HANDLE; + + std::vector imgAvailSems; + std::vector renderDoneSems; + std::vector inFlightFences; + std::vector imgInFlight; + uint32_t currentFrame = 0; + + VkSampler sampler = VK_NULL_HANDLE; + VkDescriptorPool winTexPool = VK_NULL_HANDLE; + + std::atomic needsRender{false}; + std::thread renderThread; + std::atomic isRunning{false}; + std::atomic fbResized{false}; + std::mutex renderMutex; + std::mutex dirtyMutex; + std::condition_variable dirtyCV; + std::shared_mutex frameMutex; + + void createInstance(); + void createSurface(); + void pickPhysicalDevice(); + void createLogicalDevice(); + void createSwapchain(); + void createRenderPass(); + void createDSLayout(); + void createPipeline(bool blend, VkPipeline& out); + void createFramebuffers(); + void createCmdPool(); + void createSampler(); + void createWinTexPool(); + void createCursorPipeline(); + void createCursorDS(); + void createCmdBufs(); + void createSyncObjects(); + void cleanupSwapchain(); + + bool createWinTexResources(WinTex& wt, int w, int h); + bool importAHBToWinTex(WinTex& wt, AHardwareBuffer* ahb); + bool ensureScanoutLocalAhb(int w, int h, uint32_t ahbFormat); + void cleanupAllAHBCache(); + void flushDeleteQueue(); + void destroyWinTex(WinTex& wt); + void ensureCursorTex(short w, short h); + void cleanupCursorTex(); + void ensureCursorStaging(VkDeviceSize sz); + + void recordCmdBuf(VkCommandBuffer cb, uint32_t imgIdx, + const std::vector& draws, + VkBuffer cursorUpload, bool hasCursorUpload, + float ox, float oy, float sx, float sy, float cw, float ch, + short ptrX, short ptrY, short curHotX, short curHotY, + short curW, short curH, bool curVis); + void renderLoop(); + void renderFrame(); + + uint32_t findMemType(uint32_t filter, VkMemoryPropertyFlags props); + void createBuffer(VkDeviceSize sz, VkBufferUsageFlags usage, + VkMemoryPropertyFlags props, VkBuffer& buf, VkDeviceMemory& mem); + VkCommandBuffer beginOneTime(); + void endOneTime(VkCommandBuffer cmd); + void transition(VkCommandBuffer cmd, VkImage img, + VkImageLayout oldL, VkImageLayout newL, + VkAccessFlags srcA, VkAccessFlags dstA, + VkPipelineStageFlags srcS, VkPipelineStageFlags dstS); + VkShaderModule makeShader(const uint32_t* code, size_t sz); +}; diff --git a/app/src/main/cpp/winlator/vulkan_jni.cpp b/app/src/main/cpp/winlator/vulkan_jni.cpp new file mode 100644 index 0000000000..f4f506f982 --- /dev/null +++ b/app/src/main/cpp/winlator/vulkan_jni.cpp @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include +#include +#include +// Use the angle-bracket form so the CMake include path resolves correctly. +// (adrenotools is at extras/adrenotools/ in GameNative, not adjacent to winlator/) +#include +#include "VulkanRendererContext.h" + +static void* openAdrenotoolsDriver(const char* driverPath, const char* libraryName, const char* nativeLibDir) { + if (!driverPath || !libraryName || !nativeLibDir) return nullptr; + if (access(driverPath, F_OK) != 0) { + __android_log_print(ANDROID_LOG_ERROR,"Winlator_Renderer", + "openAdrenotoolsDriver: driverPath not accessible: %s", driverPath); + return nullptr; + } + char tmpdir[512]; + snprintf(tmpdir, sizeof(tmpdir), "%stemp", driverPath); + mkdir(tmpdir, S_IRWXU | S_IRWXG); + __android_log_print(ANDROID_LOG_DEBUG,"Winlator_Renderer", + "openAdrenotoolsDriver: driverPath=%s lib=%s nativeLibDir=%s tmp=%s", + driverPath, libraryName, nativeLibDir, tmpdir); + setenv("ADRENOTOOLS_DRIVER_PATH", driverPath, 1); + setenv("ADRENOTOOLS_DRIVER_NAME", libraryName, 1); + setenv("ADRENOTOOLS_HOOKS_PATH", nativeLibDir, 1); + const char* redirectDir = getenv("ADRENOTOOLS_REDIRECT_DIR"); + int featureFlags = ADRENOTOOLS_DRIVER_CUSTOM; + if (redirectDir && redirectDir[0] != '\0') { + featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT; + } else { + unsetenv("ADRENOTOOLS_DRIVER_FILE_REDIRECT"); + } + void* handle = adrenotools_open_libvulkan( + RTLD_LOCAL | RTLD_NOW, + featureFlags, + tmpdir, + nativeLibDir, + driverPath, + libraryName, + (redirectDir && redirectDir[0] != '\0') ? redirectDir : nullptr, + nullptr); + if (!handle) { + __android_log_print(ANDROID_LOG_ERROR,"Winlator_Renderer", + "openAdrenotoolsDriver: adrenotools_open_libvulkan failed"); + } else { + __android_log_print(ANDROID_LOG_DEBUG,"Winlator_Renderer", + "openAdrenotoolsDriver: SUCCESS handle=%p", handle); + } + return handle; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeInit( + JNIEnv* env, jobject, jobject surface, jint w, jint h, + jstring jDriverPath, jstring jLibraryName, jstring jNativeLibDir) +{ + ANativeWindow* win = ANativeWindow_fromSurface(env, surface); + if (!win) return 0; + void* adrenotoolsHandle = nullptr; + if (jDriverPath && jLibraryName && jNativeLibDir) { + const char* dp = env->GetStringUTFChars(jDriverPath, nullptr); + const char* lib = env->GetStringUTFChars(jLibraryName, nullptr); + const char* nld = env->GetStringUTFChars(jNativeLibDir, nullptr); + adrenotoolsHandle = openAdrenotoolsDriver(dp, lib, nld); + env->ReleaseStringUTFChars(jDriverPath, dp); + env->ReleaseStringUTFChars(jLibraryName, lib); + env->ReleaseStringUTFChars(jNativeLibDir, nld); + } + try { return reinterpret_cast(new VulkanRendererContext(win, w, h, adrenotoolsHandle)); } + catch (...) { + ANativeWindow_release(win); + if (adrenotoolsHandle) dlclose(adrenotoolsHandle); + return 0; + } +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeResize(JNIEnv*, jobject, jlong h, jint w, jint ht) { + auto* r=reinterpret_cast(h); if (r) r->onSurfaceResized(w,ht); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeDestroy(JNIEnv*, jobject, jlong h) { + delete reinterpret_cast(h); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeUpdateWindowContent( + JNIEnv* env, jobject, jlong handle, jlong id, jobject buf, jshort w, jshort h, jshort stride, jint x, jint y) +{ + auto* r=reinterpret_cast(handle); + if (!r||!buf) return; + void* px=env->GetDirectBufferAddress(buf); + if (px && env->GetDirectBufferCapacity(buf)>=(jlong)w*h*4) + r->updateWindowContent(id,px,w,h,stride,x,y); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeUpdateWindowContentAHB( + JNIEnv*, jobject, jlong handle, jlong id, jlong ahbPtr, jshort w, jshort h, jint x, jint y) +{ + auto* r=reinterpret_cast(handle); + if (r&&ahbPtr) r->updateWindowContentAHB(id,reinterpret_cast(ahbPtr),w,h,x,y); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetTransform( + JNIEnv*, jobject, jlong handle, jfloat ox, jfloat oy, jfloat sx, jfloat sy) +{ + auto* r=reinterpret_cast(handle); if (r) r->setTransform(ox,oy,sx,sy); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetPointerPos(JNIEnv*, jobject, jlong handle, jshort x, jshort y) { + auto* r=reinterpret_cast(handle); if (r) r->updatePointerPosition(x,y); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetCursorVisible(JNIEnv*, jobject, jlong handle, jboolean v) { + auto* r=reinterpret_cast(handle); if (r) r->setCursorVisible(v); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeUpdateCursorImage( + JNIEnv* env, jobject, jlong handle, jobject buf, jshort w, jshort h, jshort hotX, jshort hotY) +{ + auto* r=reinterpret_cast(handle); + if (!r||!buf) return; + void* px=env->GetDirectBufferAddress(buf); + if (px && env->GetDirectBufferCapacity(buf)>=(jlong)w*h*4) + r->updateCursorImage(px,w,h,hotX,hotY); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetRenderList( + JNIEnv* env, jobject, jlong handle, jlongArray jids, jintArray jxs, jintArray jys, jint count) +{ + auto* r=reinterpret_cast(handle); + if (!r||count<=0) return; + jlong* ids=env->GetLongArrayElements(jids,nullptr); + jint* xs =env->GetIntArrayElements(jxs,nullptr); + jint* ys =env->GetIntArrayElements(jys,nullptr); + r->setRenderList(reinterpret_cast(ids),xs,ys,count); + env->ReleaseLongArrayElements(jids,ids,JNI_ABORT); + env->ReleaseIntArrayElements(jxs,xs,JNI_ABORT); + env->ReleaseIntArrayElements(jys,ys,JNI_ABORT); +} +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeRemoveWindow(JNIEnv*, jobject, jlong handle, jlong id) { + auto* r=reinterpret_cast(handle); if (r) r->removeWindow(id); +} + + + + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeInitScanout(JNIEnv*, jobject, jlong handle) { + auto* r = reinterpret_cast(handle); + if (r) r->initScanout(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeDestroyScanout(JNIEnv*, jobject, jlong handle) { + auto* r = reinterpret_cast(handle); + if (r) r->destroyScanout(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeScanoutSetBuffer( + JNIEnv*, jobject, jlong handle, jlong ahbPtr, jint x, jint y, jint w, jint h, jint fenceFd) +{ + auto* r = reinterpret_cast(handle); + if (r && ahbPtr) { + r->scanoutSetBuffer(reinterpret_cast(ahbPtr), x, y, w, h, (int)fenceFd); + } else if (fenceFd >= 0) { + ::close((int)fenceFd); + } +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeScanoutSetCursorImage( + JNIEnv* env, jobject, jlong handle, jobject buf, jshort w, jshort h, jshort stride) +{ + auto* r = reinterpret_cast(handle); + if (!r || !buf) return; + void* px = env->GetDirectBufferAddress(buf); + if (px && env->GetDirectBufferCapacity(buf) >= (jlong)w*h*4) + r->scanoutSetCursorImage(px, w, h, stride); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeScanoutSetCursorPos( + JNIEnv*, jobject, jlong handle, jshort x, jshort y, jshort hotX, jshort hotY) +{ + auto* r = reinterpret_cast(handle); + if (r) r->scanoutSetCursorPos(x, y, hotX, hotY); +} + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeIsScanoutActive(JNIEnv*, jobject, jlong handle) { + auto* r = reinterpret_cast(handle); + return r ? (jboolean)r->scanoutActive.load() : JNI_FALSE; +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeScanoutSetDst( + JNIEnv*, jobject, jlong handle, jint x, jint y, jint w, jint h) +{ + auto* r = reinterpret_cast(handle); + if (r) r->scanoutSetDst(x, y, w, h); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetScanoutWindow( + JNIEnv* env, jobject, jlong handle, jobject gameSurface, jobject cursorSurface) +{ + auto* r = reinterpret_cast(handle); + if (!r) return; + ANativeWindow* gw = ANativeWindow_fromSurface(env, gameSurface); + ANativeWindow* cw = ANativeWindow_fromSurface(env, cursorSurface); + if (!gw || !cw) { + if (gw) ANativeWindow_release(gw); + if (cw) ANativeWindow_release(cw); + r->initScanout(); + return; + } + r->initScanoutFromWindows(gw, cw); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetVerboseLog(JNIEnv*, jobject, jlong handle, jboolean v) { + auto* r = reinterpret_cast(handle); + if (r) r->setVerboseLog((bool)v); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeDumpRendererInfo(JNIEnv*, jobject, jlong handle) { + auto* r = reinterpret_cast(handle); + if (r) r->dumpRendererInfo(); +} + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeIsGameFrameDelivered(JNIEnv*, jobject, jlong handle) { + auto* r = reinterpret_cast(handle); + return r ? (jboolean)r->gameFrameDelivered.load() : JNI_FALSE; +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetFilterMode(JNIEnv*, jobject, jlong handle, jint mode) { + auto* r = reinterpret_cast(handle); + if (r) r->setFilterMode((int)mode); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetSwapRB(JNIEnv*, jobject, jlong handle, jboolean enabled) { + auto* r = reinterpret_cast(handle); + if (r) r->setSwapRB(enabled == JNI_TRUE); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetPresentMode(JNIEnv*, jobject, jlong handle, jint mode) { + auto* r = reinterpret_cast(handle); + if (r) r->setPresentMode((VkPresentModeKHR)mode); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetEffect(JNIEnv*, jobject, jlong handle, jint effectId, jfloat sharpness) { + auto* r = reinterpret_cast(handle); + if (r) r->setEffect((int)effectId, (float)sharpness); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeSetColorAdjustment(JNIEnv*, jobject, jlong handle, jfloat brightness, jfloat contrast, jfloat gamma) { + auto* r = reinterpret_cast(handle); + if (r) r->setColorAdjustment((float)brightness, (float)contrast, (float)gamma); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeDetachSurface(JNIEnv*, jobject, jlong handle) { + auto* r = reinterpret_cast(handle); + if (r) r->detachSurface(); +} + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_winlator_renderer_VulkanRenderer_nativeReattachSurface(JNIEnv* env, jobject, jlong handle, jobject surface) { + auto* r = reinterpret_cast(handle); + if (!r || !surface) return JNI_FALSE; + ANativeWindow* win = ANativeWindow_fromSurface(env, surface); + if (!win) return JNI_FALSE; + bool ok = r->reattachSurface(win); + if (ok && r->scanoutActive.load()) { + r->destroyScanout(); + } + return (jboolean)ok; +} diff --git a/app/src/main/cpp/winlator/window.frag b/app/src/main/cpp/winlator/window.frag new file mode 100644 index 0000000000..0e2a040708 --- /dev/null +++ b/app/src/main/cpp/winlator/window.frag @@ -0,0 +1,114 @@ +#version 450 +layout(binding = 0) uniform sampler2D texSampler; +layout(push_constant) uniform PC { + float ndcX0, ndcY0, ndcX1, ndcY1; + int effectId; + float sharpness; + float resW; + float resH; + float brightness; + float contrast; + float gamma; +} pc; + +layout(location = 0) in vec2 fragTexCoord; +layout(location = 0) out vec4 outColor; + +vec3 applyFSR(vec2 uv, float sharp) { + vec2 texel = 1.0 / max(vec2(pc.resW, pc.resH), vec2(1.0)); + vec3 c = texture(texSampler, uv).rgb; + vec3 t = texture(texSampler, uv + vec2( 0.0, -texel.y)).rgb; + vec3 b = texture(texSampler, uv + vec2( 0.0, texel.y)).rgb; + vec3 l = texture(texSampler, uv + vec2(-texel.x, 0.0 )).rgb; + vec3 r = texture(texSampler, uv + vec2( texel.x, 0.0 )).rgb; + + vec3 mnRGB = min(c, min(min(t, b), min(l, r))); + vec3 mxRGB = max(c, max(max(t, b), max(l, r))); + + vec3 num = min(mnRGB, 1.0 - mxRGB); + vec3 denom = mxRGB; + vec3 wRGB = sqrt(clamp(num / max(denom, 1e-4), 0.0, 1.0)); + float w = (wRGB.r + wRGB.g + wRGB.b) * 0.333; + + float lobe = w * mix(-0.125, -0.200, sharp); + return clamp((lobe * (t + b + l + r) + c) / (1.0 + 4.0 * lobe), 0.0, 1.0); +} + +vec3 applyDLS(vec2 uv, float sharp) { + vec2 texel = 1.0 / max(vec2(pc.resW, pc.resH), vec2(1.0)); + float SAT = 1.0 + sharp * 0.20; + float CON = 1.0 + sharp * 0.12; + float SHARP = sharp * 1.2; + + vec3 orig = texture(texSampler, uv).rgb; + vec3 c = clamp((orig - 0.5) * CON + 0.5, 0.0, 1.0); + float gray = dot(c, vec3(0.299,0.587,0.114)); + c = mix(vec3(gray), c, SAT); + + vec3 blur = (texture(texSampler, uv + vec2( 0.0, -texel.y)).rgb + + texture(texSampler, uv + vec2( 0.0, texel.y)).rgb + + texture(texSampler, uv + vec2(-texel.x, 0.0 )).rgb + + texture(texSampler, uv + vec2( texel.x, 0.0 )).rgb) * 0.25; + return clamp(c + (orig - blur) * SHARP, 0.0, 1.0); +} + +vec3 applyCRT(vec2 uv) { + float CA = 1.0025; + vec4 fc = texture(texSampler, uv); + fc.r = texture(texSampler, (uv-0.5)*CA+0.5).r; + fc.b = texture(texSampler, (uv-0.5)/CA+0.5).b; + float sx = abs(sin(uv.x*1024.0)*0.5*0.125); + float sy = abs(sin(uv.y*1024.0)*0.5*0.375); + return mix(fc.rgb, vec3(0.0), sx+sy); +} + +vec3 applyHDR(vec2 uv) { + vec2 px = 1.0 / max(vec2(pc.resW, pc.resH), vec2(1.0)); + vec3 c = texture(texSampler, uv).rgb; + float r1=0.793, r2=0.870; + vec3 b1=vec3(0.0), b2=vec3(0.0); + vec2 offs[8] = vec2[](vec2(1.5,-1.5),vec2(-1.5,-1.5),vec2(1.5,1.5),vec2(-1.5,1.5), + vec2(0.0,-2.5),vec2(0.0,2.5),vec2(-2.5,0.0),vec2(2.5,0.0)); + for(int i=0;i<8;i++){ + b1+=texture(texSampler,uv+offs[i]*r1*px).rgb; + b2+=texture(texSampler,uv+offs[i]*r2*px).rgb; + } + b1*=0.005; b2*=0.010; + float dist=r2-r1; + vec3 HDR=(c+(b2-b1))*dist; + return clamp(pow(abs(HDR+c),vec3(1.30))+HDR, 0.0, 1.0); +} + +vec3 applyNatural(vec2 uv) { + mat3 toYIQ = mat3(0.299, 0.596, 0.212, + 0.587,-0.275,-0.523, + 0.114,-0.321, 0.311); + mat3 toRGB = mat3(1.0, 1.0, 1.0, + 0.95568806,-0.27158179,-1.10817732, + 0.61985809,-0.64687381, 1.70506455); + vec3 c = texture(texSampler, uv).rgb; + vec3 t = c * toYIQ; + t = vec3(pow(t.r,1.12), t.g*1.2, t.b*1.2); + return clamp(t * toRGB, 0.0, 1.0); +} + +void main() { + vec2 uv = fragTexCoord; + vec4 c; + + if (pc.effectId == 1) c = vec4(applyFSR (uv, pc.sharpness), 1.0); + else if (pc.effectId == 2) c = vec4(applyDLS (uv, pc.sharpness), 1.0); + else if (pc.effectId == 3) c = vec4(applyCRT (uv), 1.0); + else if (pc.effectId == 4) c = vec4(applyHDR (uv), 1.0); + else if (pc.effectId == 5) c = vec4(applyNatural(uv), 1.0); + else c = texture(texSampler, uv); + + // Brightness: additive offset in [-1, 1] + c.rgb = clamp(c.rgb + vec3(pc.brightness), 0.0, 1.0); + // Contrast: scale around 0.5 in [-1, 1] range + c.rgb = clamp((c.rgb - 0.5) * (1.0 + pc.contrast) + 0.5, 0.0, 1.0); + // Gamma correction + if (pc.gamma > 0.0) c.rgb = pow(c.rgb, vec3(1.0 / pc.gamma)); + + outColor = c; +} diff --git a/app/src/main/cpp/winlator/window.vert b/app/src/main/cpp/winlator/window.vert new file mode 100644 index 0000000000..f85fbad65a --- /dev/null +++ b/app/src/main/cpp/winlator/window.vert @@ -0,0 +1,19 @@ +#version 450 + +layout(push_constant) uniform PC { + float ndcX0; + float ndcY0; + float ndcX1; + float ndcY1; +} pc; + +layout(location = 0) out vec2 fragTexCoord; + +void main() { + int xi = (gl_VertexIndex >> 1) & 1; + int yi = gl_VertexIndex & 1; + float x = xi == 1 ? pc.ndcX1 : pc.ndcX0; + float y = yi == 1 ? pc.ndcY1 : pc.ndcY0; + gl_Position = vec4(x, y, 0.0, 1.0); + fragTexCoord = vec2(float(xi), float(yi)); +} diff --git a/app/src/main/cpp/winlator/window_frag.h b/app/src/main/cpp/winlator/window_frag.h new file mode 100644 index 0000000000..e14bc8d8d5 --- /dev/null +++ b/app/src/main/cpp/winlator/window_frag.h @@ -0,0 +1,920 @@ +#pragma once +const uint32_t window_frag_code[] = { + 0x07230203,0x00010000,0x000d000a,0x0000027e, + 0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e, + 0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d, + 0x00000000,0x000001ff,0x0000027c,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002, + 0x000001c2,0x000a0004,0x475f4c47,0x4c474f4f, + 0x70635f45,0x74735f70,0x5f656c79,0x656e696c, + 0x7269645f,0x69746365,0x00006576,0x00080004, + 0x475f4c47,0x4c474f4f,0x6e695f45,0x64756c63, + 0x69645f65,0x74636572,0x00657669,0x00040005, + 0x00000004,0x6e69616d,0x00000000,0x00070005, + 0x0000000e,0x6c707061,0x52534679,0x32667628, + 0x3b31663b,0x00000000,0x00030005,0x0000000c, + 0x00007675,0x00040005,0x0000000d,0x72616873, + 0x00000070,0x00070005,0x00000012,0x6c707061, + 0x534c4479,0x32667628,0x3b31663b,0x00000000, + 0x00030005,0x00000010,0x00007675,0x00040005, + 0x00000011,0x72616873,0x00000070,0x00060005, + 0x00000016,0x6c707061,0x54524379,0x32667628, + 0x0000003b,0x00030005,0x00000015,0x00007675, + 0x00060005,0x00000019,0x6c707061,0x52444879, + 0x32667628,0x0000003b,0x00030005,0x00000018, + 0x00007675,0x00070005,0x0000001c,0x6c707061, + 0x74614e79,0x6c617275,0x32667628,0x0000003b, + 0x00030005,0x0000001b,0x00007675,0x00040005, + 0x0000001e,0x65786574,0x0000006c,0x00030005, + 0x00000021,0x00004350,0x00050006,0x00000021, + 0x00000000,0x5863646e,0x00000030,0x00050006, + 0x00000021,0x00000001,0x5963646e,0x00000030, + 0x00050006,0x00000021,0x00000002,0x5863646e, + 0x00000031,0x00050006,0x00000021,0x00000003, + 0x5963646e,0x00000031,0x00060006,0x00000021, + 0x00000004,0x65666665,0x64497463,0x00000000, + 0x00060006,0x00000021,0x00000005,0x72616873, + 0x73656e70,0x00000073,0x00050006,0x00000021, + 0x00000006,0x57736572,0x00000000,0x00050006, + 0x00000021,0x00000007,0x48736572,0x00000000, + 0x00060006,0x00000021,0x00000008,0x67697262, + 0x656e7468,0x00007373,0x00060006,0x00000021, + 0x00000009,0x746e6f63,0x74736172,0x00000000, + 0x00050006,0x00000021,0x0000000a,0x6d6d6167, + 0x00000061,0x00030005,0x00000023,0x00006370, + 0x00030005,0x00000031,0x00000063,0x00050005, + 0x00000035,0x53786574,0x6c706d61,0x00007265, + 0x00030005,0x0000003b,0x00000074,0x00030005, + 0x00000048,0x00000062,0x00030005,0x00000051, + 0x0000006c,0x00030005,0x0000005c,0x00000072, + 0x00040005,0x00000065,0x47526e6d,0x00000042, + 0x00040005,0x0000006f,0x4752786d,0x00000042, + 0x00030005,0x00000079,0x006d756e,0x00040005, + 0x0000007f,0x6f6e6564,0x0000006d,0x00040005, + 0x00000081,0x42475277,0x00000000,0x00030005, + 0x0000008c,0x00000077,0x00040005,0x00000098, + 0x65626f6c,0x00000000,0x00040005,0x000000b5, + 0x65786574,0x0000006c,0x00030005,0x000000be, + 0x00544153,0x00030005,0x000000c3,0x004e4f43, + 0x00040005,0x000000c8,0x52414853,0x00000050, + 0x00040005,0x000000cc,0x6769726f,0x00000000, + 0x00030005,0x000000d1,0x00000063,0x00040005, + 0x000000dd,0x79617267,0x00000000,0x00040005, + 0x000000ea,0x72756c62,0x00000000,0x00030005, + 0x0000011e,0x00004143,0x00030005,0x00000121, + 0x00006366,0x00030005,0x0000013c,0x00007873, + 0x00030005,0x00000146,0x00007973,0x00030005, + 0x00000159,0x00007870,0x00030005,0x00000162, + 0x00000063,0x00030005,0x00000167,0x00003172, + 0x00030005,0x00000169,0x00003272,0x00030005, + 0x0000016b,0x00003162,0x00030005,0x0000016c, + 0x00003262,0x00040005,0x00000170,0x7366666f, + 0x00000000,0x00030005,0x0000017f,0x00000069, + 0x00040005,0x000001af,0x74736964,0x00000000, + 0x00030005,0x000001b3,0x00524448,0x00040005, + 0x000001cb,0x49596f74,0x00000051,0x00040005, + 0x000001d6,0x47526f74,0x00000042,0x00030005, + 0x000001e1,0x00000063,0x00030005,0x000001e6, + 0x00000074,0x00030005,0x000001fd,0x00007675, + 0x00060005,0x000001ff,0x67617266,0x43786554, + 0x64726f6f,0x00000000,0x00030005,0x00000208, + 0x00000063,0x00040005,0x0000020a,0x61726170, + 0x0000006d,0x00040005,0x0000020c,0x61726170, + 0x0000006d,0x00040005,0x0000021b,0x61726170, + 0x0000006d,0x00040005,0x0000021d,0x61726170, + 0x0000006d,0x00040005,0x0000022c,0x61726170, + 0x0000006d,0x00040005,0x00000239,0x61726170, + 0x0000006d,0x00040005,0x00000246,0x61726170, + 0x0000006d,0x00050005,0x0000027c,0x4374756f, + 0x726f6c6f,0x00000000,0x00050048,0x00000021, + 0x00000000,0x00000023,0x00000000,0x00050048, + 0x00000021,0x00000001,0x00000023,0x00000004, + 0x00050048,0x00000021,0x00000002,0x00000023, + 0x00000008,0x00050048,0x00000021,0x00000003, + 0x00000023,0x0000000c,0x00050048,0x00000021, + 0x00000004,0x00000023,0x00000010,0x00050048, + 0x00000021,0x00000005,0x00000023,0x00000014, + 0x00050048,0x00000021,0x00000006,0x00000023, + 0x00000018,0x00050048,0x00000021,0x00000007, + 0x00000023,0x0000001c,0x00050048,0x00000021, + 0x00000008,0x00000023,0x00000020,0x00050048, + 0x00000021,0x00000009,0x00000023,0x00000024, + 0x00050048,0x00000021,0x0000000a,0x00000023, + 0x00000028,0x00030047,0x00000021,0x00000002, + 0x00040047,0x00000035,0x00000022,0x00000000, + 0x00040047,0x00000035,0x00000021,0x00000000, + 0x00040047,0x000001ff,0x0000001e,0x00000000, + 0x00040047,0x0000027c,0x0000001e,0x00000000, + 0x00020013,0x00000002,0x00030021,0x00000003, + 0x00000002,0x00030016,0x00000006,0x00000020, + 0x00040017,0x00000007,0x00000006,0x00000002, + 0x00040020,0x00000008,0x00000007,0x00000007, + 0x00040020,0x00000009,0x00000007,0x00000006, + 0x00040017,0x0000000a,0x00000006,0x00000003, + 0x00050021,0x0000000b,0x0000000a,0x00000008, + 0x00000009,0x00040021,0x00000014,0x0000000a, + 0x00000008,0x0004002b,0x00000006,0x0000001f, + 0x3f800000,0x00040015,0x00000020,0x00000020, + 0x00000001,0x000d001e,0x00000021,0x00000006, + 0x00000006,0x00000006,0x00000006,0x00000020, + 0x00000006,0x00000006,0x00000006,0x00000006, + 0x00000006,0x00000006,0x00040020,0x00000022, + 0x00000009,0x00000021,0x0004003b,0x00000022, + 0x00000023,0x00000009,0x0004002b,0x00000020, + 0x00000024,0x00000006,0x00040020,0x00000025, + 0x00000009,0x00000006,0x0004002b,0x00000020, + 0x00000028,0x00000007,0x0005002c,0x00000007, + 0x0000002c,0x0000001f,0x0000001f,0x00040020, + 0x00000030,0x00000007,0x0000000a,0x00090019, + 0x00000032,0x00000006,0x00000001,0x00000000, + 0x00000000,0x00000000,0x00000001,0x00000000, + 0x0003001b,0x00000033,0x00000032,0x00040020, + 0x00000034,0x00000000,0x00000033,0x0004003b, + 0x00000034,0x00000035,0x00000000,0x00040017, + 0x00000038,0x00000006,0x00000004,0x0004002b, + 0x00000006,0x0000003e,0x00000000,0x00040015, + 0x0000003f,0x00000020,0x00000000,0x0004002b, + 0x0000003f,0x00000040,0x00000001,0x0004002b, + 0x0000003f,0x00000054,0x00000000,0x0004002b, + 0x00000006,0x00000084,0x38d1b717,0x0004002b, + 0x0000003f,0x00000092,0x00000002,0x0004002b, + 0x00000006,0x00000096,0x3eaa7efa,0x0004002b, + 0x00000006,0x0000009a,0xbe000000,0x0004002b, + 0x00000006,0x0000009b,0xbe4ccccd,0x0004002b, + 0x00000006,0x000000aa,0x40800000,0x0004002b, + 0x00000006,0x000000c0,0x3e4ccccd,0x0004002b, + 0x00000006,0x000000c5,0x3df5c28f,0x0004002b, + 0x00000006,0x000000ca,0x3f99999a,0x0004002b, + 0x00000006,0x000000d3,0x3f000000,0x0004002b, + 0x00000006,0x000000df,0x3e991687,0x0004002b, + 0x00000006,0x000000e0,0x3f1645a2,0x0004002b, + 0x00000006,0x000000e1,0x3de978d5,0x0006002c, + 0x0000000a,0x000000e2,0x000000df,0x000000e0, + 0x000000e1,0x0004002b,0x00000006,0x00000110, + 0x3e800000,0x0004002b,0x00000006,0x0000011f, + 0x3f8051ec,0x00040020,0x00000120,0x00000007, + 0x00000038,0x0004002b,0x00000006,0x0000013f, + 0x44800000,0x0004002b,0x00000006,0x00000143, + 0x3e000000,0x0004002b,0x00000006,0x0000014c, + 0x3ec00000,0x0006002c,0x0000000a,0x00000151, + 0x0000003e,0x0000003e,0x0000003e,0x0004002b, + 0x00000006,0x00000168,0x3f4b020c,0x0004002b, + 0x00000006,0x0000016a,0x3f5eb852,0x0004002b, + 0x0000003f,0x0000016d,0x00000008,0x0004001c, + 0x0000016e,0x00000007,0x0000016d,0x00040020, + 0x0000016f,0x00000007,0x0000016e,0x0004002b, + 0x00000006,0x00000171,0x3fc00000,0x0004002b, + 0x00000006,0x00000172,0xbfc00000,0x0005002c, + 0x00000007,0x00000173,0x00000171,0x00000172, + 0x0005002c,0x00000007,0x00000174,0x00000172, + 0x00000172,0x0005002c,0x00000007,0x00000175, + 0x00000171,0x00000171,0x0005002c,0x00000007, + 0x00000176,0x00000172,0x00000171,0x0004002b, + 0x00000006,0x00000177,0xc0200000,0x0005002c, + 0x00000007,0x00000178,0x0000003e,0x00000177, + 0x0004002b,0x00000006,0x00000179,0x40200000, + 0x0005002c,0x00000007,0x0000017a,0x0000003e, + 0x00000179,0x0005002c,0x00000007,0x0000017b, + 0x00000177,0x0000003e,0x0005002c,0x00000007, + 0x0000017c,0x00000179,0x0000003e,0x000b002c, + 0x0000016e,0x0000017d,0x00000173,0x00000174, + 0x00000175,0x00000176,0x00000178,0x0000017a, + 0x0000017b,0x0000017c,0x00040020,0x0000017e, + 0x00000007,0x00000020,0x0004002b,0x00000020, + 0x00000180,0x00000000,0x0004002b,0x00000020, + 0x00000187,0x00000008,0x00020014,0x00000188, + 0x0004002b,0x00000020,0x000001a7,0x00000001, + 0x0004002b,0x00000006,0x000001a9,0x3ba3d70a, + 0x0004002b,0x00000006,0x000001ac,0x3c23d70a, + 0x0004002b,0x00000006,0x000001bf,0x3fa66666, + 0x0006002c,0x0000000a,0x000001c0,0x000001bf, + 0x000001bf,0x000001bf,0x00040018,0x000001c9, + 0x0000000a,0x00000003,0x00040020,0x000001ca, + 0x00000007,0x000001c9,0x0004002b,0x00000006, + 0x000001cc,0x3f189375,0x0004002b,0x00000006, + 0x000001cd,0x3e591687,0x0006002c,0x0000000a, + 0x000001ce,0x000000df,0x000001cc,0x000001cd, + 0x0004002b,0x00000006,0x000001cf,0xbe8ccccd, + 0x0004002b,0x00000006,0x000001d0,0xbf05e354, + 0x0006002c,0x0000000a,0x000001d1,0x000000e0, + 0x000001cf,0x000001d0,0x0004002b,0x00000006, + 0x000001d2,0xbea45a1d,0x0004002b,0x00000006, + 0x000001d3,0x3e9f3b64,0x0006002c,0x0000000a, + 0x000001d4,0x000000e1,0x000001d2,0x000001d3, + 0x0006002c,0x000001c9,0x000001d5,0x000001ce, + 0x000001d1,0x000001d4,0x0006002c,0x0000000a, + 0x000001d7,0x0000001f,0x0000001f,0x0000001f, + 0x0004002b,0x00000006,0x000001d8,0x3f74a7f9, + 0x0004002b,0x00000006,0x000001d9,0xbe8b0cc5, + 0x0004002b,0x00000006,0x000001da,0xbf8dd8c1, + 0x0006002c,0x0000000a,0x000001db,0x000001d8, + 0x000001d9,0x000001da,0x0004002b,0x00000006, + 0x000001dc,0x3f1eaf05,0x0004002b,0x00000006, + 0x000001dd,0xbf259986,0x0004002b,0x00000006, + 0x000001de,0x3fda3f8e,0x0006002c,0x0000000a, + 0x000001df,0x000001dc,0x000001dd,0x000001de, + 0x0006002c,0x000001c9,0x000001e0,0x000001d7, + 0x000001db,0x000001df,0x0004002b,0x00000006, + 0x000001ec,0x3f8f5c29,0x00040020,0x000001fe, + 0x00000001,0x00000007,0x0004003b,0x000001fe, + 0x000001ff,0x00000001,0x0004002b,0x00000020, + 0x00000201,0x00000004,0x00040020,0x00000202, + 0x00000009,0x00000020,0x0004002b,0x00000020, + 0x00000209,0x00000005,0x0004002b,0x00000020, + 0x00000217,0x00000002,0x0004002b,0x00000020, + 0x00000228,0x00000003,0x0004002b,0x00000020, + 0x00000260,0x00000009,0x0004002b,0x00000020, + 0x0000026c,0x0000000a,0x00040020,0x0000027b, + 0x00000003,0x00000038,0x0004003b,0x0000027b, + 0x0000027c,0x00000003,0x00050036,0x00000002, + 0x00000004,0x00000000,0x00000003,0x000200f8, + 0x00000005,0x0004003b,0x00000008,0x000001fd, + 0x00000007,0x0004003b,0x00000120,0x00000208, + 0x00000007,0x0004003b,0x00000008,0x0000020a, + 0x00000007,0x0004003b,0x00000009,0x0000020c, + 0x00000007,0x0004003b,0x00000008,0x0000021b, + 0x00000007,0x0004003b,0x00000009,0x0000021d, + 0x00000007,0x0004003b,0x00000008,0x0000022c, + 0x00000007,0x0004003b,0x00000008,0x00000239, + 0x00000007,0x0004003b,0x00000008,0x00000246, + 0x00000007,0x0004003d,0x00000007,0x00000200, + 0x000001ff,0x0003003e,0x000001fd,0x00000200, + 0x00050041,0x00000202,0x00000203,0x00000023, + 0x00000201,0x0004003d,0x00000020,0x00000204, + 0x00000203,0x000500aa,0x00000188,0x00000205, + 0x00000204,0x000001a7,0x000300f7,0x00000207, + 0x00000000,0x000400fa,0x00000205,0x00000206, + 0x00000214,0x000200f8,0x00000206,0x0004003d, + 0x00000007,0x0000020b,0x000001fd,0x0003003e, + 0x0000020a,0x0000020b,0x00050041,0x00000025, + 0x0000020d,0x00000023,0x00000209,0x0004003d, + 0x00000006,0x0000020e,0x0000020d,0x0003003e, + 0x0000020c,0x0000020e,0x00060039,0x0000000a, + 0x0000020f,0x0000000e,0x0000020a,0x0000020c, + 0x00050051,0x00000006,0x00000210,0x0000020f, + 0x00000000,0x00050051,0x00000006,0x00000211, + 0x0000020f,0x00000001,0x00050051,0x00000006, + 0x00000212,0x0000020f,0x00000002,0x00070050, + 0x00000038,0x00000213,0x00000210,0x00000211, + 0x00000212,0x0000001f,0x0003003e,0x00000208, + 0x00000213,0x000200f9,0x00000207,0x000200f8, + 0x00000214,0x00050041,0x00000202,0x00000215, + 0x00000023,0x00000201,0x0004003d,0x00000020, + 0x00000216,0x00000215,0x000500aa,0x00000188, + 0x00000218,0x00000216,0x00000217,0x000300f7, + 0x0000021a,0x00000000,0x000400fa,0x00000218, + 0x00000219,0x00000225,0x000200f8,0x00000219, + 0x0004003d,0x00000007,0x0000021c,0x000001fd, + 0x0003003e,0x0000021b,0x0000021c,0x00050041, + 0x00000025,0x0000021e,0x00000023,0x00000209, + 0x0004003d,0x00000006,0x0000021f,0x0000021e, + 0x0003003e,0x0000021d,0x0000021f,0x00060039, + 0x0000000a,0x00000220,0x00000012,0x0000021b, + 0x0000021d,0x00050051,0x00000006,0x00000221, + 0x00000220,0x00000000,0x00050051,0x00000006, + 0x00000222,0x00000220,0x00000001,0x00050051, + 0x00000006,0x00000223,0x00000220,0x00000002, + 0x00070050,0x00000038,0x00000224,0x00000221, + 0x00000222,0x00000223,0x0000001f,0x0003003e, + 0x00000208,0x00000224,0x000200f9,0x0000021a, + 0x000200f8,0x00000225,0x00050041,0x00000202, + 0x00000226,0x00000023,0x00000201,0x0004003d, + 0x00000020,0x00000227,0x00000226,0x000500aa, + 0x00000188,0x00000229,0x00000227,0x00000228, + 0x000300f7,0x0000022b,0x00000000,0x000400fa, + 0x00000229,0x0000022a,0x00000233,0x000200f8, + 0x0000022a,0x0004003d,0x00000007,0x0000022d, + 0x000001fd,0x0003003e,0x0000022c,0x0000022d, + 0x00050039,0x0000000a,0x0000022e,0x00000016, + 0x0000022c,0x00050051,0x00000006,0x0000022f, + 0x0000022e,0x00000000,0x00050051,0x00000006, + 0x00000230,0x0000022e,0x00000001,0x00050051, + 0x00000006,0x00000231,0x0000022e,0x00000002, + 0x00070050,0x00000038,0x00000232,0x0000022f, + 0x00000230,0x00000231,0x0000001f,0x0003003e, + 0x00000208,0x00000232,0x000200f9,0x0000022b, + 0x000200f8,0x00000233,0x00050041,0x00000202, + 0x00000234,0x00000023,0x00000201,0x0004003d, + 0x00000020,0x00000235,0x00000234,0x000500aa, + 0x00000188,0x00000236,0x00000235,0x00000201, + 0x000300f7,0x00000238,0x00000000,0x000400fa, + 0x00000236,0x00000237,0x00000240,0x000200f8, + 0x00000237,0x0004003d,0x00000007,0x0000023a, + 0x000001fd,0x0003003e,0x00000239,0x0000023a, + 0x00050039,0x0000000a,0x0000023b,0x00000019, + 0x00000239,0x00050051,0x00000006,0x0000023c, + 0x0000023b,0x00000000,0x00050051,0x00000006, + 0x0000023d,0x0000023b,0x00000001,0x00050051, + 0x00000006,0x0000023e,0x0000023b,0x00000002, + 0x00070050,0x00000038,0x0000023f,0x0000023c, + 0x0000023d,0x0000023e,0x0000001f,0x0003003e, + 0x00000208,0x0000023f,0x000200f9,0x00000238, + 0x000200f8,0x00000240,0x00050041,0x00000202, + 0x00000241,0x00000023,0x00000201,0x0004003d, + 0x00000020,0x00000242,0x00000241,0x000500aa, + 0x00000188,0x00000243,0x00000242,0x00000209, + 0x000300f7,0x00000245,0x00000000,0x000400fa, + 0x00000243,0x00000244,0x0000024d,0x000200f8, + 0x00000244,0x0004003d,0x00000007,0x00000247, + 0x000001fd,0x0003003e,0x00000246,0x00000247, + 0x00050039,0x0000000a,0x00000248,0x0000001c, + 0x00000246,0x00050051,0x00000006,0x00000249, + 0x00000248,0x00000000,0x00050051,0x00000006, + 0x0000024a,0x00000248,0x00000001,0x00050051, + 0x00000006,0x0000024b,0x00000248,0x00000002, + 0x00070050,0x00000038,0x0000024c,0x00000249, + 0x0000024a,0x0000024b,0x0000001f,0x0003003e, + 0x00000208,0x0000024c,0x000200f9,0x00000245, + 0x000200f8,0x0000024d,0x0004003d,0x00000033, + 0x0000024e,0x00000035,0x0004003d,0x00000007, + 0x0000024f,0x000001fd,0x00050057,0x00000038, + 0x00000250,0x0000024e,0x0000024f,0x0003003e, + 0x00000208,0x00000250,0x000200f9,0x00000245, + 0x000200f8,0x00000245,0x000200f9,0x00000238, + 0x000200f8,0x00000238,0x000200f9,0x0000022b, + 0x000200f8,0x0000022b,0x000200f9,0x0000021a, + 0x000200f8,0x0000021a,0x000200f9,0x00000207, + 0x000200f8,0x00000207,0x0004003d,0x00000038, + 0x00000251,0x00000208,0x0008004f,0x0000000a, + 0x00000252,0x00000251,0x00000251,0x00000000, + 0x00000001,0x00000002,0x00050041,0x00000025, + 0x00000253,0x00000023,0x00000187,0x0004003d, + 0x00000006,0x00000254,0x00000253,0x00060050, + 0x0000000a,0x00000255,0x00000254,0x00000254, + 0x00000254,0x00050081,0x0000000a,0x00000256, + 0x00000252,0x00000255,0x00060050,0x0000000a, + 0x00000257,0x0000003e,0x0000003e,0x0000003e, + 0x00060050,0x0000000a,0x00000258,0x0000001f, + 0x0000001f,0x0000001f,0x0008000c,0x0000000a, + 0x00000259,0x00000001,0x0000002b,0x00000256, + 0x00000257,0x00000258,0x0004003d,0x00000038, + 0x0000025a,0x00000208,0x0009004f,0x00000038, + 0x0000025b,0x0000025a,0x00000259,0x00000004, + 0x00000005,0x00000006,0x00000003,0x0003003e, + 0x00000208,0x0000025b,0x0004003d,0x00000038, + 0x0000025c,0x00000208,0x0008004f,0x0000000a, + 0x0000025d,0x0000025c,0x0000025c,0x00000000, + 0x00000001,0x00000002,0x00060050,0x0000000a, + 0x0000025e,0x000000d3,0x000000d3,0x000000d3, + 0x00050083,0x0000000a,0x0000025f,0x0000025d, + 0x0000025e,0x00050041,0x00000025,0x00000261, + 0x00000023,0x00000260,0x0004003d,0x00000006, + 0x00000262,0x00000261,0x00050081,0x00000006, + 0x00000263,0x0000001f,0x00000262,0x0005008e, + 0x0000000a,0x00000264,0x0000025f,0x00000263, + 0x00060050,0x0000000a,0x00000265,0x000000d3, + 0x000000d3,0x000000d3,0x00050081,0x0000000a, + 0x00000266,0x00000264,0x00000265,0x00060050, + 0x0000000a,0x00000267,0x0000003e,0x0000003e, + 0x0000003e,0x00060050,0x0000000a,0x00000268, + 0x0000001f,0x0000001f,0x0000001f,0x0008000c, + 0x0000000a,0x00000269,0x00000001,0x0000002b, + 0x00000266,0x00000267,0x00000268,0x0004003d, + 0x00000038,0x0000026a,0x00000208,0x0009004f, + 0x00000038,0x0000026b,0x0000026a,0x00000269, + 0x00000004,0x00000005,0x00000006,0x00000003, + 0x0003003e,0x00000208,0x0000026b,0x00050041, + 0x00000025,0x0000026d,0x00000023,0x0000026c, + 0x0004003d,0x00000006,0x0000026e,0x0000026d, + 0x000500ba,0x00000188,0x0000026f,0x0000026e, + 0x0000003e,0x000300f7,0x00000271,0x00000000, + 0x000400fa,0x0000026f,0x00000270,0x00000271, + 0x000200f8,0x00000270,0x0004003d,0x00000038, + 0x00000272,0x00000208,0x0008004f,0x0000000a, + 0x00000273,0x00000272,0x00000272,0x00000000, + 0x00000001,0x00000002,0x00050041,0x00000025, + 0x00000274,0x00000023,0x0000026c,0x0004003d, + 0x00000006,0x00000275,0x00000274,0x00050088, + 0x00000006,0x00000276,0x0000001f,0x00000275, + 0x00060050,0x0000000a,0x00000277,0x00000276, + 0x00000276,0x00000276,0x0007000c,0x0000000a, + 0x00000278,0x00000001,0x0000001a,0x00000273, + 0x00000277,0x0004003d,0x00000038,0x00000279, + 0x00000208,0x0009004f,0x00000038,0x0000027a, + 0x00000279,0x00000278,0x00000004,0x00000005, + 0x00000006,0x00000003,0x0003003e,0x00000208, + 0x0000027a,0x000200f9,0x00000271,0x000200f8, + 0x00000271,0x0004003d,0x00000038,0x0000027d, + 0x00000208,0x0003003e,0x0000027c,0x0000027d, + 0x000100fd,0x00010038,0x00050036,0x0000000a, + 0x0000000e,0x00000000,0x0000000b,0x00030037, + 0x00000008,0x0000000c,0x00030037,0x00000009, + 0x0000000d,0x000200f8,0x0000000f,0x0004003b, + 0x00000008,0x0000001e,0x00000007,0x0004003b, + 0x00000030,0x00000031,0x00000007,0x0004003b, + 0x00000030,0x0000003b,0x00000007,0x0004003b, + 0x00000030,0x00000048,0x00000007,0x0004003b, + 0x00000030,0x00000051,0x00000007,0x0004003b, + 0x00000030,0x0000005c,0x00000007,0x0004003b, + 0x00000030,0x00000065,0x00000007,0x0004003b, + 0x00000030,0x0000006f,0x00000007,0x0004003b, + 0x00000030,0x00000079,0x00000007,0x0004003b, + 0x00000030,0x0000007f,0x00000007,0x0004003b, + 0x00000030,0x00000081,0x00000007,0x0004003b, + 0x00000009,0x0000008c,0x00000007,0x0004003b, + 0x00000009,0x00000098,0x00000007,0x00050041, + 0x00000025,0x00000026,0x00000023,0x00000024, + 0x0004003d,0x00000006,0x00000027,0x00000026, + 0x00050041,0x00000025,0x00000029,0x00000023, + 0x00000028,0x0004003d,0x00000006,0x0000002a, + 0x00000029,0x00050050,0x00000007,0x0000002b, + 0x00000027,0x0000002a,0x0007000c,0x00000007, + 0x0000002d,0x00000001,0x00000028,0x0000002b, + 0x0000002c,0x00050050,0x00000007,0x0000002e, + 0x0000001f,0x0000001f,0x00050088,0x00000007, + 0x0000002f,0x0000002e,0x0000002d,0x0003003e, + 0x0000001e,0x0000002f,0x0004003d,0x00000033, + 0x00000036,0x00000035,0x0004003d,0x00000007, + 0x00000037,0x0000000c,0x00050057,0x00000038, + 0x00000039,0x00000036,0x00000037,0x0008004f, + 0x0000000a,0x0000003a,0x00000039,0x00000039, + 0x00000000,0x00000001,0x00000002,0x0003003e, + 0x00000031,0x0000003a,0x0004003d,0x00000033, + 0x0000003c,0x00000035,0x0004003d,0x00000007, + 0x0000003d,0x0000000c,0x00050041,0x00000009, + 0x00000041,0x0000001e,0x00000040,0x0004003d, + 0x00000006,0x00000042,0x00000041,0x0004007f, + 0x00000006,0x00000043,0x00000042,0x00050050, + 0x00000007,0x00000044,0x0000003e,0x00000043, + 0x00050081,0x00000007,0x00000045,0x0000003d, + 0x00000044,0x00050057,0x00000038,0x00000046, + 0x0000003c,0x00000045,0x0008004f,0x0000000a, + 0x00000047,0x00000046,0x00000046,0x00000000, + 0x00000001,0x00000002,0x0003003e,0x0000003b, + 0x00000047,0x0004003d,0x00000033,0x00000049, + 0x00000035,0x0004003d,0x00000007,0x0000004a, + 0x0000000c,0x00050041,0x00000009,0x0000004b, + 0x0000001e,0x00000040,0x0004003d,0x00000006, + 0x0000004c,0x0000004b,0x00050050,0x00000007, + 0x0000004d,0x0000003e,0x0000004c,0x00050081, + 0x00000007,0x0000004e,0x0000004a,0x0000004d, + 0x00050057,0x00000038,0x0000004f,0x00000049, + 0x0000004e,0x0008004f,0x0000000a,0x00000050, + 0x0000004f,0x0000004f,0x00000000,0x00000001, + 0x00000002,0x0003003e,0x00000048,0x00000050, + 0x0004003d,0x00000033,0x00000052,0x00000035, + 0x0004003d,0x00000007,0x00000053,0x0000000c, + 0x00050041,0x00000009,0x00000055,0x0000001e, + 0x00000054,0x0004003d,0x00000006,0x00000056, + 0x00000055,0x0004007f,0x00000006,0x00000057, + 0x00000056,0x00050050,0x00000007,0x00000058, + 0x00000057,0x0000003e,0x00050081,0x00000007, + 0x00000059,0x00000053,0x00000058,0x00050057, + 0x00000038,0x0000005a,0x00000052,0x00000059, + 0x0008004f,0x0000000a,0x0000005b,0x0000005a, + 0x0000005a,0x00000000,0x00000001,0x00000002, + 0x0003003e,0x00000051,0x0000005b,0x0004003d, + 0x00000033,0x0000005d,0x00000035,0x0004003d, + 0x00000007,0x0000005e,0x0000000c,0x00050041, + 0x00000009,0x0000005f,0x0000001e,0x00000054, + 0x0004003d,0x00000006,0x00000060,0x0000005f, + 0x00050050,0x00000007,0x00000061,0x00000060, + 0x0000003e,0x00050081,0x00000007,0x00000062, + 0x0000005e,0x00000061,0x00050057,0x00000038, + 0x00000063,0x0000005d,0x00000062,0x0008004f, + 0x0000000a,0x00000064,0x00000063,0x00000063, + 0x00000000,0x00000001,0x00000002,0x0003003e, + 0x0000005c,0x00000064,0x0004003d,0x0000000a, + 0x00000066,0x00000031,0x0004003d,0x0000000a, + 0x00000067,0x0000003b,0x0004003d,0x0000000a, + 0x00000068,0x00000048,0x0007000c,0x0000000a, + 0x00000069,0x00000001,0x00000025,0x00000067, + 0x00000068,0x0004003d,0x0000000a,0x0000006a, + 0x00000051,0x0004003d,0x0000000a,0x0000006b, + 0x0000005c,0x0007000c,0x0000000a,0x0000006c, + 0x00000001,0x00000025,0x0000006a,0x0000006b, + 0x0007000c,0x0000000a,0x0000006d,0x00000001, + 0x00000025,0x00000069,0x0000006c,0x0007000c, + 0x0000000a,0x0000006e,0x00000001,0x00000025, + 0x00000066,0x0000006d,0x0003003e,0x00000065, + 0x0000006e,0x0004003d,0x0000000a,0x00000070, + 0x00000031,0x0004003d,0x0000000a,0x00000071, + 0x0000003b,0x0004003d,0x0000000a,0x00000072, + 0x00000048,0x0007000c,0x0000000a,0x00000073, + 0x00000001,0x00000028,0x00000071,0x00000072, + 0x0004003d,0x0000000a,0x00000074,0x00000051, + 0x0004003d,0x0000000a,0x00000075,0x0000005c, + 0x0007000c,0x0000000a,0x00000076,0x00000001, + 0x00000028,0x00000074,0x00000075,0x0007000c, + 0x0000000a,0x00000077,0x00000001,0x00000028, + 0x00000073,0x00000076,0x0007000c,0x0000000a, + 0x00000078,0x00000001,0x00000028,0x00000070, + 0x00000077,0x0003003e,0x0000006f,0x00000078, + 0x0004003d,0x0000000a,0x0000007a,0x00000065, + 0x0004003d,0x0000000a,0x0000007b,0x0000006f, + 0x00060050,0x0000000a,0x0000007c,0x0000001f, + 0x0000001f,0x0000001f,0x00050083,0x0000000a, + 0x0000007d,0x0000007c,0x0000007b,0x0007000c, + 0x0000000a,0x0000007e,0x00000001,0x00000025, + 0x0000007a,0x0000007d,0x0003003e,0x00000079, + 0x0000007e,0x0004003d,0x0000000a,0x00000080, + 0x0000006f,0x0003003e,0x0000007f,0x00000080, + 0x0004003d,0x0000000a,0x00000082,0x00000079, + 0x0004003d,0x0000000a,0x00000083,0x0000007f, + 0x00060050,0x0000000a,0x00000085,0x00000084, + 0x00000084,0x00000084,0x0007000c,0x0000000a, + 0x00000086,0x00000001,0x00000028,0x00000083, + 0x00000085,0x00050088,0x0000000a,0x00000087, + 0x00000082,0x00000086,0x00060050,0x0000000a, + 0x00000088,0x0000003e,0x0000003e,0x0000003e, + 0x00060050,0x0000000a,0x00000089,0x0000001f, + 0x0000001f,0x0000001f,0x0008000c,0x0000000a, + 0x0000008a,0x00000001,0x0000002b,0x00000087, + 0x00000088,0x00000089,0x0006000c,0x0000000a, + 0x0000008b,0x00000001,0x0000001f,0x0000008a, + 0x0003003e,0x00000081,0x0000008b,0x00050041, + 0x00000009,0x0000008d,0x00000081,0x00000054, + 0x0004003d,0x00000006,0x0000008e,0x0000008d, + 0x00050041,0x00000009,0x0000008f,0x00000081, + 0x00000040,0x0004003d,0x00000006,0x00000090, + 0x0000008f,0x00050081,0x00000006,0x00000091, + 0x0000008e,0x00000090,0x00050041,0x00000009, + 0x00000093,0x00000081,0x00000092,0x0004003d, + 0x00000006,0x00000094,0x00000093,0x00050081, + 0x00000006,0x00000095,0x00000091,0x00000094, + 0x00050085,0x00000006,0x00000097,0x00000095, + 0x00000096,0x0003003e,0x0000008c,0x00000097, + 0x0004003d,0x00000006,0x00000099,0x0000008c, + 0x0004003d,0x00000006,0x0000009c,0x0000000d, + 0x0008000c,0x00000006,0x0000009d,0x00000001, + 0x0000002e,0x0000009a,0x0000009b,0x0000009c, + 0x00050085,0x00000006,0x0000009e,0x00000099, + 0x0000009d,0x0003003e,0x00000098,0x0000009e, + 0x0004003d,0x00000006,0x0000009f,0x00000098, + 0x0004003d,0x0000000a,0x000000a0,0x0000003b, + 0x0004003d,0x0000000a,0x000000a1,0x00000048, + 0x00050081,0x0000000a,0x000000a2,0x000000a0, + 0x000000a1,0x0004003d,0x0000000a,0x000000a3, + 0x00000051,0x00050081,0x0000000a,0x000000a4, + 0x000000a2,0x000000a3,0x0004003d,0x0000000a, + 0x000000a5,0x0000005c,0x00050081,0x0000000a, + 0x000000a6,0x000000a4,0x000000a5,0x0005008e, + 0x0000000a,0x000000a7,0x000000a6,0x0000009f, + 0x0004003d,0x0000000a,0x000000a8,0x00000031, + 0x00050081,0x0000000a,0x000000a9,0x000000a7, + 0x000000a8,0x0004003d,0x00000006,0x000000ab, + 0x00000098,0x00050085,0x00000006,0x000000ac, + 0x000000aa,0x000000ab,0x00050081,0x00000006, + 0x000000ad,0x0000001f,0x000000ac,0x00060050, + 0x0000000a,0x000000ae,0x000000ad,0x000000ad, + 0x000000ad,0x00050088,0x0000000a,0x000000af, + 0x000000a9,0x000000ae,0x00060050,0x0000000a, + 0x000000b0,0x0000003e,0x0000003e,0x0000003e, + 0x00060050,0x0000000a,0x000000b1,0x0000001f, + 0x0000001f,0x0000001f,0x0008000c,0x0000000a, + 0x000000b2,0x00000001,0x0000002b,0x000000af, + 0x000000b0,0x000000b1,0x000200fe,0x000000b2, + 0x00010038,0x00050036,0x0000000a,0x00000012, + 0x00000000,0x0000000b,0x00030037,0x00000008, + 0x00000010,0x00030037,0x00000009,0x00000011, + 0x000200f8,0x00000013,0x0004003b,0x00000008, + 0x000000b5,0x00000007,0x0004003b,0x00000009, + 0x000000be,0x00000007,0x0004003b,0x00000009, + 0x000000c3,0x00000007,0x0004003b,0x00000009, + 0x000000c8,0x00000007,0x0004003b,0x00000030, + 0x000000cc,0x00000007,0x0004003b,0x00000030, + 0x000000d1,0x00000007,0x0004003b,0x00000009, + 0x000000dd,0x00000007,0x0004003b,0x00000030, + 0x000000ea,0x00000007,0x00050041,0x00000025, + 0x000000b6,0x00000023,0x00000024,0x0004003d, + 0x00000006,0x000000b7,0x000000b6,0x00050041, + 0x00000025,0x000000b8,0x00000023,0x00000028, + 0x0004003d,0x00000006,0x000000b9,0x000000b8, + 0x00050050,0x00000007,0x000000ba,0x000000b7, + 0x000000b9,0x0007000c,0x00000007,0x000000bb, + 0x00000001,0x00000028,0x000000ba,0x0000002c, + 0x00050050,0x00000007,0x000000bc,0x0000001f, + 0x0000001f,0x00050088,0x00000007,0x000000bd, + 0x000000bc,0x000000bb,0x0003003e,0x000000b5, + 0x000000bd,0x0004003d,0x00000006,0x000000bf, + 0x00000011,0x00050085,0x00000006,0x000000c1, + 0x000000bf,0x000000c0,0x00050081,0x00000006, + 0x000000c2,0x0000001f,0x000000c1,0x0003003e, + 0x000000be,0x000000c2,0x0004003d,0x00000006, + 0x000000c4,0x00000011,0x00050085,0x00000006, + 0x000000c6,0x000000c4,0x000000c5,0x00050081, + 0x00000006,0x000000c7,0x0000001f,0x000000c6, + 0x0003003e,0x000000c3,0x000000c7,0x0004003d, + 0x00000006,0x000000c9,0x00000011,0x00050085, + 0x00000006,0x000000cb,0x000000c9,0x000000ca, + 0x0003003e,0x000000c8,0x000000cb,0x0004003d, + 0x00000033,0x000000cd,0x00000035,0x0004003d, + 0x00000007,0x000000ce,0x00000010,0x00050057, + 0x00000038,0x000000cf,0x000000cd,0x000000ce, + 0x0008004f,0x0000000a,0x000000d0,0x000000cf, + 0x000000cf,0x00000000,0x00000001,0x00000002, + 0x0003003e,0x000000cc,0x000000d0,0x0004003d, + 0x0000000a,0x000000d2,0x000000cc,0x00060050, + 0x0000000a,0x000000d4,0x000000d3,0x000000d3, + 0x000000d3,0x00050083,0x0000000a,0x000000d5, + 0x000000d2,0x000000d4,0x0004003d,0x00000006, + 0x000000d6,0x000000c3,0x0005008e,0x0000000a, + 0x000000d7,0x000000d5,0x000000d6,0x00060050, + 0x0000000a,0x000000d8,0x000000d3,0x000000d3, + 0x000000d3,0x00050081,0x0000000a,0x000000d9, + 0x000000d7,0x000000d8,0x00060050,0x0000000a, + 0x000000da,0x0000003e,0x0000003e,0x0000003e, + 0x00060050,0x0000000a,0x000000db,0x0000001f, + 0x0000001f,0x0000001f,0x0008000c,0x0000000a, + 0x000000dc,0x00000001,0x0000002b,0x000000d9, + 0x000000da,0x000000db,0x0003003e,0x000000d1, + 0x000000dc,0x0004003d,0x0000000a,0x000000de, + 0x000000d1,0x00050094,0x00000006,0x000000e3, + 0x000000de,0x000000e2,0x0003003e,0x000000dd, + 0x000000e3,0x0004003d,0x00000006,0x000000e4, + 0x000000dd,0x00060050,0x0000000a,0x000000e5, + 0x000000e4,0x000000e4,0x000000e4,0x0004003d, + 0x0000000a,0x000000e6,0x000000d1,0x0004003d, + 0x00000006,0x000000e7,0x000000be,0x00060050, + 0x0000000a,0x000000e8,0x000000e7,0x000000e7, + 0x000000e7,0x0008000c,0x0000000a,0x000000e9, + 0x00000001,0x0000002e,0x000000e5,0x000000e6, + 0x000000e8,0x0003003e,0x000000d1,0x000000e9, + 0x0004003d,0x00000033,0x000000eb,0x00000035, + 0x0004003d,0x00000007,0x000000ec,0x00000010, + 0x00050041,0x00000009,0x000000ed,0x000000b5, + 0x00000040,0x0004003d,0x00000006,0x000000ee, + 0x000000ed,0x0004007f,0x00000006,0x000000ef, + 0x000000ee,0x00050050,0x00000007,0x000000f0, + 0x0000003e,0x000000ef,0x00050081,0x00000007, + 0x000000f1,0x000000ec,0x000000f0,0x00050057, + 0x00000038,0x000000f2,0x000000eb,0x000000f1, + 0x0008004f,0x0000000a,0x000000f3,0x000000f2, + 0x000000f2,0x00000000,0x00000001,0x00000002, + 0x0004003d,0x00000033,0x000000f4,0x00000035, + 0x0004003d,0x00000007,0x000000f5,0x00000010, + 0x00050041,0x00000009,0x000000f6,0x000000b5, + 0x00000040,0x0004003d,0x00000006,0x000000f7, + 0x000000f6,0x00050050,0x00000007,0x000000f8, + 0x0000003e,0x000000f7,0x00050081,0x00000007, + 0x000000f9,0x000000f5,0x000000f8,0x00050057, + 0x00000038,0x000000fa,0x000000f4,0x000000f9, + 0x0008004f,0x0000000a,0x000000fb,0x000000fa, + 0x000000fa,0x00000000,0x00000001,0x00000002, + 0x00050081,0x0000000a,0x000000fc,0x000000f3, + 0x000000fb,0x0004003d,0x00000033,0x000000fd, + 0x00000035,0x0004003d,0x00000007,0x000000fe, + 0x00000010,0x00050041,0x00000009,0x000000ff, + 0x000000b5,0x00000054,0x0004003d,0x00000006, + 0x00000100,0x000000ff,0x0004007f,0x00000006, + 0x00000101,0x00000100,0x00050050,0x00000007, + 0x00000102,0x00000101,0x0000003e,0x00050081, + 0x00000007,0x00000103,0x000000fe,0x00000102, + 0x00050057,0x00000038,0x00000104,0x000000fd, + 0x00000103,0x0008004f,0x0000000a,0x00000105, + 0x00000104,0x00000104,0x00000000,0x00000001, + 0x00000002,0x00050081,0x0000000a,0x00000106, + 0x000000fc,0x00000105,0x0004003d,0x00000033, + 0x00000107,0x00000035,0x0004003d,0x00000007, + 0x00000108,0x00000010,0x00050041,0x00000009, + 0x00000109,0x000000b5,0x00000054,0x0004003d, + 0x00000006,0x0000010a,0x00000109,0x00050050, + 0x00000007,0x0000010b,0x0000010a,0x0000003e, + 0x00050081,0x00000007,0x0000010c,0x00000108, + 0x0000010b,0x00050057,0x00000038,0x0000010d, + 0x00000107,0x0000010c,0x0008004f,0x0000000a, + 0x0000010e,0x0000010d,0x0000010d,0x00000000, + 0x00000001,0x00000002,0x00050081,0x0000000a, + 0x0000010f,0x00000106,0x0000010e,0x0005008e, + 0x0000000a,0x00000111,0x0000010f,0x00000110, + 0x0003003e,0x000000ea,0x00000111,0x0004003d, + 0x0000000a,0x00000112,0x000000d1,0x0004003d, + 0x0000000a,0x00000113,0x000000cc,0x0004003d, + 0x0000000a,0x00000114,0x000000ea,0x00050083, + 0x0000000a,0x00000115,0x00000113,0x00000114, + 0x0004003d,0x00000006,0x00000116,0x000000c8, + 0x0005008e,0x0000000a,0x00000117,0x00000115, + 0x00000116,0x00050081,0x0000000a,0x00000118, + 0x00000112,0x00000117,0x00060050,0x0000000a, + 0x00000119,0x0000003e,0x0000003e,0x0000003e, + 0x00060050,0x0000000a,0x0000011a,0x0000001f, + 0x0000001f,0x0000001f,0x0008000c,0x0000000a, + 0x0000011b,0x00000001,0x0000002b,0x00000118, + 0x00000119,0x0000011a,0x000200fe,0x0000011b, + 0x00010038,0x00050036,0x0000000a,0x00000016, + 0x00000000,0x00000014,0x00030037,0x00000008, + 0x00000015,0x000200f8,0x00000017,0x0004003b, + 0x00000009,0x0000011e,0x00000007,0x0004003b, + 0x00000120,0x00000121,0x00000007,0x0004003b, + 0x00000009,0x0000013c,0x00000007,0x0004003b, + 0x00000009,0x00000146,0x00000007,0x0003003e, + 0x0000011e,0x0000011f,0x0004003d,0x00000033, + 0x00000122,0x00000035,0x0004003d,0x00000007, + 0x00000123,0x00000015,0x00050057,0x00000038, + 0x00000124,0x00000122,0x00000123,0x0003003e, + 0x00000121,0x00000124,0x0004003d,0x00000033, + 0x00000125,0x00000035,0x0004003d,0x00000007, + 0x00000126,0x00000015,0x00050050,0x00000007, + 0x00000127,0x000000d3,0x000000d3,0x00050083, + 0x00000007,0x00000128,0x00000126,0x00000127, + 0x0004003d,0x00000006,0x00000129,0x0000011e, + 0x0005008e,0x00000007,0x0000012a,0x00000128, + 0x00000129,0x00050050,0x00000007,0x0000012b, + 0x000000d3,0x000000d3,0x00050081,0x00000007, + 0x0000012c,0x0000012a,0x0000012b,0x00050057, + 0x00000038,0x0000012d,0x00000125,0x0000012c, + 0x00050051,0x00000006,0x0000012e,0x0000012d, + 0x00000000,0x00050041,0x00000009,0x0000012f, + 0x00000121,0x00000054,0x0003003e,0x0000012f, + 0x0000012e,0x0004003d,0x00000033,0x00000130, + 0x00000035,0x0004003d,0x00000007,0x00000131, + 0x00000015,0x00050050,0x00000007,0x00000132, + 0x000000d3,0x000000d3,0x00050083,0x00000007, + 0x00000133,0x00000131,0x00000132,0x0004003d, + 0x00000006,0x00000134,0x0000011e,0x00050050, + 0x00000007,0x00000135,0x00000134,0x00000134, + 0x00050088,0x00000007,0x00000136,0x00000133, + 0x00000135,0x00050050,0x00000007,0x00000137, + 0x000000d3,0x000000d3,0x00050081,0x00000007, + 0x00000138,0x00000136,0x00000137,0x00050057, + 0x00000038,0x00000139,0x00000130,0x00000138, + 0x00050051,0x00000006,0x0000013a,0x00000139, + 0x00000002,0x00050041,0x00000009,0x0000013b, + 0x00000121,0x00000092,0x0003003e,0x0000013b, + 0x0000013a,0x00050041,0x00000009,0x0000013d, + 0x00000015,0x00000054,0x0004003d,0x00000006, + 0x0000013e,0x0000013d,0x00050085,0x00000006, + 0x00000140,0x0000013e,0x0000013f,0x0006000c, + 0x00000006,0x00000141,0x00000001,0x0000000d, + 0x00000140,0x00050085,0x00000006,0x00000142, + 0x00000141,0x000000d3,0x00050085,0x00000006, + 0x00000144,0x00000142,0x00000143,0x0006000c, + 0x00000006,0x00000145,0x00000001,0x00000004, + 0x00000144,0x0003003e,0x0000013c,0x00000145, + 0x00050041,0x00000009,0x00000147,0x00000015, + 0x00000040,0x0004003d,0x00000006,0x00000148, + 0x00000147,0x00050085,0x00000006,0x00000149, + 0x00000148,0x0000013f,0x0006000c,0x00000006, + 0x0000014a,0x00000001,0x0000000d,0x00000149, + 0x00050085,0x00000006,0x0000014b,0x0000014a, + 0x000000d3,0x00050085,0x00000006,0x0000014d, + 0x0000014b,0x0000014c,0x0006000c,0x00000006, + 0x0000014e,0x00000001,0x00000004,0x0000014d, + 0x0003003e,0x00000146,0x0000014e,0x0004003d, + 0x00000038,0x0000014f,0x00000121,0x0008004f, + 0x0000000a,0x00000150,0x0000014f,0x0000014f, + 0x00000000,0x00000001,0x00000002,0x0004003d, + 0x00000006,0x00000152,0x0000013c,0x0004003d, + 0x00000006,0x00000153,0x00000146,0x00050081, + 0x00000006,0x00000154,0x00000152,0x00000153, + 0x00060050,0x0000000a,0x00000155,0x00000154, + 0x00000154,0x00000154,0x0008000c,0x0000000a, + 0x00000156,0x00000001,0x0000002e,0x00000150, + 0x00000151,0x00000155,0x000200fe,0x00000156, + 0x00010038,0x00050036,0x0000000a,0x00000019, + 0x00000000,0x00000014,0x00030037,0x00000008, + 0x00000018,0x000200f8,0x0000001a,0x0004003b, + 0x00000008,0x00000159,0x00000007,0x0004003b, + 0x00000030,0x00000162,0x00000007,0x0004003b, + 0x00000009,0x00000167,0x00000007,0x0004003b, + 0x00000009,0x00000169,0x00000007,0x0004003b, + 0x00000030,0x0000016b,0x00000007,0x0004003b, + 0x00000030,0x0000016c,0x00000007,0x0004003b, + 0x0000016f,0x00000170,0x00000007,0x0004003b, + 0x0000017e,0x0000017f,0x00000007,0x0004003b, + 0x00000009,0x000001af,0x00000007,0x0004003b, + 0x00000030,0x000001b3,0x00000007,0x00050041, + 0x00000025,0x0000015a,0x00000023,0x00000024, + 0x0004003d,0x00000006,0x0000015b,0x0000015a, + 0x00050041,0x00000025,0x0000015c,0x00000023, + 0x00000028,0x0004003d,0x00000006,0x0000015d, + 0x0000015c,0x00050050,0x00000007,0x0000015e, + 0x0000015b,0x0000015d,0x0007000c,0x00000007, + 0x0000015f,0x00000001,0x00000028,0x0000015e, + 0x0000002c,0x00050050,0x00000007,0x00000160, + 0x0000001f,0x0000001f,0x00050088,0x00000007, + 0x00000161,0x00000160,0x0000015f,0x0003003e, + 0x00000159,0x00000161,0x0004003d,0x00000033, + 0x00000163,0x00000035,0x0004003d,0x00000007, + 0x00000164,0x00000018,0x00050057,0x00000038, + 0x00000165,0x00000163,0x00000164,0x0008004f, + 0x0000000a,0x00000166,0x00000165,0x00000165, + 0x00000000,0x00000001,0x00000002,0x0003003e, + 0x00000162,0x00000166,0x0003003e,0x00000167, + 0x00000168,0x0003003e,0x00000169,0x0000016a, + 0x0003003e,0x0000016b,0x00000151,0x0003003e, + 0x0000016c,0x00000151,0x0003003e,0x00000170, + 0x0000017d,0x0003003e,0x0000017f,0x00000180, + 0x000200f9,0x00000181,0x000200f8,0x00000181, + 0x000400f6,0x00000183,0x00000184,0x00000000, + 0x000200f9,0x00000185,0x000200f8,0x00000185, + 0x0004003d,0x00000020,0x00000186,0x0000017f, + 0x000500b1,0x00000188,0x00000189,0x00000186, + 0x00000187,0x000400fa,0x00000189,0x00000182, + 0x00000183,0x000200f8,0x00000182,0x0004003d, + 0x00000033,0x0000018a,0x00000035,0x0004003d, + 0x00000007,0x0000018b,0x00000018,0x0004003d, + 0x00000020,0x0000018c,0x0000017f,0x00050041, + 0x00000008,0x0000018d,0x00000170,0x0000018c, + 0x0004003d,0x00000007,0x0000018e,0x0000018d, + 0x0004003d,0x00000006,0x0000018f,0x00000167, + 0x0005008e,0x00000007,0x00000190,0x0000018e, + 0x0000018f,0x0004003d,0x00000007,0x00000191, + 0x00000159,0x00050085,0x00000007,0x00000192, + 0x00000190,0x00000191,0x00050081,0x00000007, + 0x00000193,0x0000018b,0x00000192,0x00050057, + 0x00000038,0x00000194,0x0000018a,0x00000193, + 0x0008004f,0x0000000a,0x00000195,0x00000194, + 0x00000194,0x00000000,0x00000001,0x00000002, + 0x0004003d,0x0000000a,0x00000196,0x0000016b, + 0x00050081,0x0000000a,0x00000197,0x00000196, + 0x00000195,0x0003003e,0x0000016b,0x00000197, + 0x0004003d,0x00000033,0x00000198,0x00000035, + 0x0004003d,0x00000007,0x00000199,0x00000018, + 0x0004003d,0x00000020,0x0000019a,0x0000017f, + 0x00050041,0x00000008,0x0000019b,0x00000170, + 0x0000019a,0x0004003d,0x00000007,0x0000019c, + 0x0000019b,0x0004003d,0x00000006,0x0000019d, + 0x00000169,0x0005008e,0x00000007,0x0000019e, + 0x0000019c,0x0000019d,0x0004003d,0x00000007, + 0x0000019f,0x00000159,0x00050085,0x00000007, + 0x000001a0,0x0000019e,0x0000019f,0x00050081, + 0x00000007,0x000001a1,0x00000199,0x000001a0, + 0x00050057,0x00000038,0x000001a2,0x00000198, + 0x000001a1,0x0008004f,0x0000000a,0x000001a3, + 0x000001a2,0x000001a2,0x00000000,0x00000001, + 0x00000002,0x0004003d,0x0000000a,0x000001a4, + 0x0000016c,0x00050081,0x0000000a,0x000001a5, + 0x000001a4,0x000001a3,0x0003003e,0x0000016c, + 0x000001a5,0x000200f9,0x00000184,0x000200f8, + 0x00000184,0x0004003d,0x00000020,0x000001a6, + 0x0000017f,0x00050080,0x00000020,0x000001a8, + 0x000001a6,0x000001a7,0x0003003e,0x0000017f, + 0x000001a8,0x000200f9,0x00000181,0x000200f8, + 0x00000183,0x0004003d,0x0000000a,0x000001aa, + 0x0000016b,0x0005008e,0x0000000a,0x000001ab, + 0x000001aa,0x000001a9,0x0003003e,0x0000016b, + 0x000001ab,0x0004003d,0x0000000a,0x000001ad, + 0x0000016c,0x0005008e,0x0000000a,0x000001ae, + 0x000001ad,0x000001ac,0x0003003e,0x0000016c, + 0x000001ae,0x0004003d,0x00000006,0x000001b0, + 0x00000169,0x0004003d,0x00000006,0x000001b1, + 0x00000167,0x00050083,0x00000006,0x000001b2, + 0x000001b0,0x000001b1,0x0003003e,0x000001af, + 0x000001b2,0x0004003d,0x0000000a,0x000001b4, + 0x00000162,0x0004003d,0x0000000a,0x000001b5, + 0x0000016c,0x0004003d,0x0000000a,0x000001b6, + 0x0000016b,0x00050083,0x0000000a,0x000001b7, + 0x000001b5,0x000001b6,0x00050081,0x0000000a, + 0x000001b8,0x000001b4,0x000001b7,0x0004003d, + 0x00000006,0x000001b9,0x000001af,0x0005008e, + 0x0000000a,0x000001ba,0x000001b8,0x000001b9, + 0x0003003e,0x000001b3,0x000001ba,0x0004003d, + 0x0000000a,0x000001bb,0x000001b3,0x0004003d, + 0x0000000a,0x000001bc,0x00000162,0x00050081, + 0x0000000a,0x000001bd,0x000001bb,0x000001bc, + 0x0006000c,0x0000000a,0x000001be,0x00000001, + 0x00000004,0x000001bd,0x0007000c,0x0000000a, + 0x000001c1,0x00000001,0x0000001a,0x000001be, + 0x000001c0,0x0004003d,0x0000000a,0x000001c2, + 0x000001b3,0x00050081,0x0000000a,0x000001c3, + 0x000001c1,0x000001c2,0x00060050,0x0000000a, + 0x000001c4,0x0000003e,0x0000003e,0x0000003e, + 0x00060050,0x0000000a,0x000001c5,0x0000001f, + 0x0000001f,0x0000001f,0x0008000c,0x0000000a, + 0x000001c6,0x00000001,0x0000002b,0x000001c3, + 0x000001c4,0x000001c5,0x000200fe,0x000001c6, + 0x00010038,0x00050036,0x0000000a,0x0000001c, + 0x00000000,0x00000014,0x00030037,0x00000008, + 0x0000001b,0x000200f8,0x0000001d,0x0004003b, + 0x000001ca,0x000001cb,0x00000007,0x0004003b, + 0x000001ca,0x000001d6,0x00000007,0x0004003b, + 0x00000030,0x000001e1,0x00000007,0x0004003b, + 0x00000030,0x000001e6,0x00000007,0x0003003e, + 0x000001cb,0x000001d5,0x0003003e,0x000001d6, + 0x000001e0,0x0004003d,0x00000033,0x000001e2, + 0x00000035,0x0004003d,0x00000007,0x000001e3, + 0x0000001b,0x00050057,0x00000038,0x000001e4, + 0x000001e2,0x000001e3,0x0008004f,0x0000000a, + 0x000001e5,0x000001e4,0x000001e4,0x00000000, + 0x00000001,0x00000002,0x0003003e,0x000001e1, + 0x000001e5,0x0004003d,0x0000000a,0x000001e7, + 0x000001e1,0x0004003d,0x000001c9,0x000001e8, + 0x000001cb,0x00050090,0x0000000a,0x000001e9, + 0x000001e7,0x000001e8,0x0003003e,0x000001e6, + 0x000001e9,0x00050041,0x00000009,0x000001ea, + 0x000001e6,0x00000054,0x0004003d,0x00000006, + 0x000001eb,0x000001ea,0x0007000c,0x00000006, + 0x000001ed,0x00000001,0x0000001a,0x000001eb, + 0x000001ec,0x00050041,0x00000009,0x000001ee, + 0x000001e6,0x00000040,0x0004003d,0x00000006, + 0x000001ef,0x000001ee,0x00050085,0x00000006, + 0x000001f0,0x000001ef,0x000000ca,0x00050041, + 0x00000009,0x000001f1,0x000001e6,0x00000092, + 0x0004003d,0x00000006,0x000001f2,0x000001f1, + 0x00050085,0x00000006,0x000001f3,0x000001f2, + 0x000000ca,0x00060050,0x0000000a,0x000001f4, + 0x000001ed,0x000001f0,0x000001f3,0x0003003e, + 0x000001e6,0x000001f4,0x0004003d,0x0000000a, + 0x000001f5,0x000001e6,0x0004003d,0x000001c9, + 0x000001f6,0x000001d6,0x00050090,0x0000000a, + 0x000001f7,0x000001f5,0x000001f6,0x00060050, + 0x0000000a,0x000001f8,0x0000003e,0x0000003e, + 0x0000003e,0x00060050,0x0000000a,0x000001f9, + 0x0000001f,0x0000001f,0x0000001f,0x0008000c, + 0x0000000a,0x000001fa,0x00000001,0x0000002b, + 0x000001f7,0x000001f8,0x000001f9,0x000200fe, + 0x000001fa,0x00010038, +}; diff --git a/app/src/main/cpp/winlator/window_vert.h b/app/src/main/cpp/winlator/window_vert.h new file mode 100644 index 0000000000..a798cf0de8 --- /dev/null +++ b/app/src/main/cpp/winlator/window_vert.h @@ -0,0 +1,68 @@ + // 1114.3.0 + #pragma once +const uint32_t window_vert_code[] = { + 0x07230203,0x00010000,0x0008000b,0x0000004a,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0008000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000a,0x0000003a,0x00000044, + 0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d,0x00000000,0x00030005, + 0x00000008,0x00006978,0x00060005,0x0000000a,0x565f6c67,0x65747265,0x646e4978,0x00007865, + 0x00030005,0x0000000f,0x00006979,0x00030005,0x00000014,0x00000078,0x00030005,0x0000001b, + 0x00004350,0x00050006,0x0000001b,0x00000000,0x5863646e,0x00000030,0x00050006,0x0000001b, + 0x00000001,0x5963646e,0x00000030,0x00050006,0x0000001b,0x00000002,0x5863646e,0x00000031, + 0x00050006,0x0000001b,0x00000003,0x5963646e,0x00000031,0x00030005,0x0000001d,0x00006370, + 0x00030005,0x00000027,0x00000079,0x00060005,0x00000038,0x505f6c67,0x65567265,0x78657472, + 0x00000000,0x00060006,0x00000038,0x00000000,0x505f6c67,0x7469736f,0x006e6f69,0x00070006, + 0x00000038,0x00000001,0x505f6c67,0x746e696f,0x657a6953,0x00000000,0x00070006,0x00000038, + 0x00000002,0x435f6c67,0x4470696c,0x61747369,0x0065636e,0x00070006,0x00000038,0x00000003, + 0x435f6c67,0x446c6c75,0x61747369,0x0065636e,0x00030005,0x0000003a,0x00000000,0x00060005, + 0x00000044,0x67617266,0x43786554,0x64726f6f,0x00000000,0x00040047,0x0000000a,0x0000000b, + 0x0000002a,0x00050048,0x0000001b,0x00000000,0x00000023,0x00000000,0x00050048,0x0000001b, + 0x00000001,0x00000023,0x00000004,0x00050048,0x0000001b,0x00000002,0x00000023,0x00000008, + 0x00050048,0x0000001b,0x00000003,0x00000023,0x0000000c,0x00030047,0x0000001b,0x00000002, + 0x00050048,0x00000038,0x00000000,0x0000000b,0x00000000,0x00050048,0x00000038,0x00000001, + 0x0000000b,0x00000001,0x00050048,0x00000038,0x00000002,0x0000000b,0x00000003,0x00050048, + 0x00000038,0x00000003,0x0000000b,0x00000004,0x00030047,0x00000038,0x00000002,0x00040047, + 0x00000044,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, + 0x00040015,0x00000006,0x00000020,0x00000001,0x00040020,0x00000007,0x00000007,0x00000006, + 0x00040020,0x00000009,0x00000001,0x00000006,0x0004003b,0x00000009,0x0000000a,0x00000001, + 0x0004002b,0x00000006,0x0000000c,0x00000001,0x00030016,0x00000012,0x00000020,0x00040020, + 0x00000013,0x00000007,0x00000012,0x00020014,0x00000016,0x0006001e,0x0000001b,0x00000012, + 0x00000012,0x00000012,0x00000012,0x00040020,0x0000001c,0x00000009,0x0000001b,0x0004003b, + 0x0000001c,0x0000001d,0x00000009,0x0004002b,0x00000006,0x0000001e,0x00000002,0x00040020, + 0x0000001f,0x00000009,0x00000012,0x0004002b,0x00000006,0x00000023,0x00000000,0x0004002b, + 0x00000006,0x0000002d,0x00000003,0x00040017,0x00000034,0x00000012,0x00000004,0x00040015, + 0x00000035,0x00000020,0x00000000,0x0004002b,0x00000035,0x00000036,0x00000001,0x0004001c, + 0x00000037,0x00000012,0x00000036,0x0006001e,0x00000038,0x00000034,0x00000012,0x00000037, + 0x00000037,0x00040020,0x00000039,0x00000003,0x00000038,0x0004003b,0x00000039,0x0000003a, + 0x00000003,0x0004002b,0x00000012,0x0000003d,0x00000000,0x0004002b,0x00000012,0x0000003e, + 0x3f800000,0x00040020,0x00000040,0x00000003,0x00000034,0x00040017,0x00000042,0x00000012, + 0x00000002,0x00040020,0x00000043,0x00000003,0x00000042,0x0004003b,0x00000043,0x00000044, + 0x00000003,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005, + 0x0004003b,0x00000007,0x00000008,0x00000007,0x0004003b,0x00000007,0x0000000f,0x00000007, + 0x0004003b,0x00000013,0x00000014,0x00000007,0x0004003b,0x00000013,0x00000018,0x00000007, + 0x0004003b,0x00000013,0x00000027,0x00000007,0x0004003b,0x00000013,0x0000002a,0x00000007, + 0x0004003d,0x00000006,0x0000000b,0x0000000a,0x000500c3,0x00000006,0x0000000d,0x0000000b, + 0x0000000c,0x000500c7,0x00000006,0x0000000e,0x0000000d,0x0000000c,0x0003003e,0x00000008, + 0x0000000e,0x0004003d,0x00000006,0x00000010,0x0000000a,0x000500c7,0x00000006,0x00000011, + 0x00000010,0x0000000c,0x0003003e,0x0000000f,0x00000011,0x0004003d,0x00000006,0x00000015, + 0x00000008,0x000500aa,0x00000016,0x00000017,0x00000015,0x0000000c,0x000300f7,0x0000001a, + 0x00000000,0x000400fa,0x00000017,0x00000019,0x00000022,0x000200f8,0x00000019,0x00050041, + 0x0000001f,0x00000020,0x0000001d,0x0000001e,0x0004003d,0x00000012,0x00000021,0x00000020, + 0x0003003e,0x00000018,0x00000021,0x000200f9,0x0000001a,0x000200f8,0x00000022,0x00050041, + 0x0000001f,0x00000024,0x0000001d,0x00000023,0x0004003d,0x00000012,0x00000025,0x00000024, + 0x0003003e,0x00000018,0x00000025,0x000200f9,0x0000001a,0x000200f8,0x0000001a,0x0004003d, + 0x00000012,0x00000026,0x00000018,0x0003003e,0x00000014,0x00000026,0x0004003d,0x00000006, + 0x00000028,0x0000000f,0x000500aa,0x00000016,0x00000029,0x00000028,0x0000000c,0x000300f7, + 0x0000002c,0x00000000,0x000400fa,0x00000029,0x0000002b,0x00000030,0x000200f8,0x0000002b, + 0x00050041,0x0000001f,0x0000002e,0x0000001d,0x0000002d,0x0004003d,0x00000012,0x0000002f, + 0x0000002e,0x0003003e,0x0000002a,0x0000002f,0x000200f9,0x0000002c,0x000200f8,0x00000030, + 0x00050041,0x0000001f,0x00000031,0x0000001d,0x0000000c,0x0004003d,0x00000012,0x00000032, + 0x00000031,0x0003003e,0x0000002a,0x00000032,0x000200f9,0x0000002c,0x000200f8,0x0000002c, + 0x0004003d,0x00000012,0x00000033,0x0000002a,0x0003003e,0x00000027,0x00000033,0x0004003d, + 0x00000012,0x0000003b,0x00000014,0x0004003d,0x00000012,0x0000003c,0x00000027,0x00070050, + 0x00000034,0x0000003f,0x0000003b,0x0000003c,0x0000003d,0x0000003e,0x00050041,0x00000040, + 0x00000041,0x0000003a,0x00000023,0x0003003e,0x00000041,0x0000003f,0x0004003d,0x00000006, + 0x00000045,0x00000008,0x0004006f,0x00000012,0x00000046,0x00000045,0x0004003d,0x00000006, + 0x00000047,0x0000000f,0x0004006f,0x00000012,0x00000048,0x00000047,0x00050050,0x00000042, + 0x00000049,0x00000046,0x00000048,0x0003003e,0x00000044,0x00000049,0x000100fd,0x00010038 +}; diff --git a/app/src/main/java/app/gamenative/ui/component/QuickMenu.kt b/app/src/main/java/app/gamenative/ui/component/QuickMenu.kt index 7b170cf32a..a691d19250 100644 --- a/app/src/main/java/app/gamenative/ui/component/QuickMenu.kt +++ b/app/src/main/java/app/gamenative/ui/component/QuickMenu.kt @@ -93,10 +93,10 @@ import app.gamenative.ui.theme.PluviaTheme import app.gamenative.ui.util.adaptivePanelWidth import app.gamenative.utils.MathUtils.normalizedProgress import com.winlator.container.Container -import com.winlator.renderer.GLRenderer +import com.winlator.renderer.VulkanRenderer import com.winlator.winhandler.ProcessInfo -import kotlinx.coroutines.delay import kotlin.math.roundToInt +import kotlinx.coroutines.delay object QuickMenuAction { const val KEYBOARD = 1 @@ -236,7 +236,7 @@ fun QuickMenu( isVisible: Boolean, onDismiss: () -> Unit, onItemSelected: (Int) -> Boolean, - renderer: GLRenderer? = null, + renderer: VulkanRenderer? = null, container: Container? = null, wineProcesses: List = emptyList(), isWineProcessesLoading: Boolean = false, @@ -278,7 +278,7 @@ fun QuickMenu( icon = Icons.Filled.Mouse, labelResId = R.string.disable_mouse_input, accentColor = PluviaTheme.colors.accentPurple, - ) + ), ) add( QuickMenuItem( @@ -286,7 +286,7 @@ fun QuickMenu( icon = Icons.Default.Keyboard, labelResId = R.string.keyboard, accentColor = PluviaTheme.colors.accentPurple, - ) + ), ) add( QuickMenuItem( @@ -294,7 +294,7 @@ fun QuickMenu( icon = Icons.Default.TouchApp, labelResId = R.string.input_controls, accentColor = PluviaTheme.colors.accentPurple, - ) + ), ) if (hasPhysicalController) { add( @@ -303,7 +303,7 @@ fun QuickMenu( icon = Icons.Default.Gamepad, labelResId = R.string.edit_physical_controller, accentColor = PluviaTheme.colors.accentPurple, - ) + ), ) } add( @@ -312,7 +312,7 @@ fun QuickMenu( icon = Icons.Default.Edit, labelResId = R.string.edit_controls, accentColor = PluviaTheme.colors.accentPurple, - ) + ), ) add( QuickMenuItem( @@ -320,15 +320,17 @@ fun QuickMenu( icon = Icons.Default.Fingerprint, labelResId = R.string.touchscreen_mode, accentColor = PluviaTheme.colors.accentPurple, - ) + ), ) } var selectedTab by rememberSaveable { mutableIntStateOf( - if (PrefManager.quickMenuLastTab == QuickMenuTab.LSFG && !isLsfgAvailable) + if (PrefManager.quickMenuLastTab == QuickMenuTab.LSFG && !isLsfgAvailable) { QuickMenuTab.HUD - else PrefManager.quickMenuLastTab + } else { + PrefManager.quickMenuLastTab + }, ) } val selectedTabLabelResId = when (selectedTab) { @@ -645,10 +647,20 @@ fun QuickMenu( } }, focusRequester = if (index == 0) controllerItemFocusRequester else null, - secondaryIcon = if (item.id == QuickMenuAction.TOUCHSCREEN_MODE && isTouchscreenModeActive) - Icons.Default.Settings else null, - onSecondaryClick = if (item.id == QuickMenuAction.TOUCHSCREEN_MODE && isTouchscreenModeActive) - onTouchGestureSettingsClick else null, + secondaryIcon = if (item.id == QuickMenuAction.TOUCHSCREEN_MODE && + isTouchscreenModeActive + ) { + Icons.Default.Settings + } else { + null + }, + onSecondaryClick = if (item.id == QuickMenuAction.TOUCHSCREEN_MODE && + isTouchscreenModeActive + ) { + onTouchGestureSettingsClick + } else { + null + }, ) } } @@ -764,7 +776,9 @@ private fun PerformanceHudQuickMenuTab( title = stringResource(R.string.performance_hud_fps_limiter), subtitle = if (limiterControlledByLsfg) { stringResource(R.string.performance_hud_fps_limiter_lsfg_override) - } else null, + } else { + null + }, enabled = fpsLimiterEnabled && !limiterControlledByLsfg, onToggle = { if (!limiterControlledByLsfg) onFpsLimiterEnabledChanged(!fpsLimiterEnabled) @@ -1217,7 +1231,7 @@ private fun QuickMenuCloseButton( ) } else { Modifier - } + }, ) .clip(shape) .background( @@ -1281,7 +1295,7 @@ private fun QuickMenuTabButton( ) } else { Modifier - } + }, ) .clip(shape) .background( @@ -1296,7 +1310,7 @@ private fun QuickMenuTabButton( Modifier.focusRequester(focusRequester) } else { Modifier - } + }, ) .onFocusChanged { if (it.isFocused && !selected) { @@ -1356,7 +1370,7 @@ private fun QuickMenuRailActionButton( color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.25f), shape = shape, ) - } + }, ) .clip(shape) .background( @@ -1371,7 +1385,7 @@ private fun QuickMenuRailActionButton( Modifier.focusRequester(focusRequester) } else { Modifier - } + }, ) .clickable( interactionSource = interactionSource, @@ -1419,7 +1433,7 @@ private fun QuickMenuChoiceChip( color = if (selected) accentColor.copy(alpha = 0.55f) else MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.25f), shape = shape, ) - } + }, ) .clip(shape) .background( @@ -1434,7 +1448,7 @@ private fun QuickMenuChoiceChip( Modifier.focusRequester(focusRequester) } else { Modifier - } + }, ) .selectable( selected = selected, @@ -1502,14 +1516,14 @@ private fun QuickMenuAdjustmentRow( ) } else { Modifier - } + }, ) .then( if (focusRequester != null) { Modifier.focusRequester(focusRequester) } else { Modifier - } + }, ) .onFocusChanged { if (!it.isFocused) { @@ -1750,14 +1764,14 @@ private fun QuickMenuToggleRow( ) } else { Modifier - } + }, ) .then( if (focusRequester != null) { Modifier.focusRequester(focusRequester) } else { Modifier - } + }, ) .selectable( selected = isFocused, @@ -1868,14 +1882,14 @@ private fun QuickMenuProcessRow( ) } else { Modifier - } + }, ) .then( if (focusRequester != null) { Modifier.focusRequester(focusRequester) } else { Modifier - } + }, ) .focusable(interactionSource = interactionSource) .onPreviewKeyEvent { keyEvent -> @@ -1883,7 +1897,8 @@ private fun QuickMenuProcessRow( when (keyEvent.nativeKeyEvent.keyCode) { KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_DPAD_CENTER, - KeyEvent.KEYCODE_ENTER -> { + KeyEvent.KEYCODE_ENTER, + -> { onEndProcess() true } @@ -1969,7 +1984,7 @@ private fun QuickMenuItemRow( ) } else { Modifier - } + }, ) .clip(shape) .then( @@ -1984,14 +1999,14 @@ private fun QuickMenuItemRow( ) } else { Modifier - } + }, ) .then( if (focusRequester != null) { Modifier.focusRequester(focusRequester) } else { Modifier - } + }, ) .selectable( selected = isFocused, @@ -2014,7 +2029,9 @@ private fun QuickMenuItemRow( .then( if (isActive) { Modifier.border(BorderStroke(2.dp, accentColor), CircleShape) - } else Modifier + } else { + Modifier + }, ) .clip(CircleShape) .background( diff --git a/app/src/main/java/app/gamenative/ui/component/ScreenEffectsPanel.kt b/app/src/main/java/app/gamenative/ui/component/ScreenEffectsPanel.kt index 1007a5909d..8ed5f4b309 100644 --- a/app/src/main/java/app/gamenative/ui/component/ScreenEffectsPanel.kt +++ b/app/src/main/java/app/gamenative/ui/component/ScreenEffectsPanel.kt @@ -73,9 +73,8 @@ import app.gamenative.ui.util.applyScreenEffectsConfig import app.gamenative.ui.util.loadScreenEffectsConfig import app.gamenative.ui.util.persistScreenEffectsConfig import com.winlator.container.Container -import com.winlator.renderer.GLRenderer +import com.winlator.renderer.VulkanRenderer import kotlinx.coroutines.delay -import kotlin.math.abs private const val SCREEN_EFFECT_PERCENT_STEP = 5f private const val SCREEN_EFFECT_GAMMA_STEP = 0.1f @@ -92,7 +91,7 @@ private fun scalingModeLabelRes(mode: Int): Int = when (mode) { @Composable fun ScreenEffectsTabContent( - renderer: GLRenderer, + renderer: VulkanRenderer, modifier: Modifier = Modifier, container: Container? = null, firstItemFocusRequester: FocusRequester? = null, @@ -307,7 +306,7 @@ fun ScreenEffectsTabContent( @Composable fun ScreenEffectsPanel( isVisible: Boolean, - renderer: GLRenderer, + renderer: VulkanRenderer, onDismiss: () -> Unit, modifier: Modifier = Modifier, container: Container? = null, @@ -423,160 +422,161 @@ fun ScreenEffectsPanel( modifier = Modifier .width(adaptivePanelWidth(420.dp)) .fillMaxHeight(), - shape = RoundedCornerShape(topStart = 24.dp, bottomStart = 24.dp), - color = MaterialTheme.colorScheme.surface, - tonalElevation = 4.dp, - shadowElevation = 24.dp, - ) { - Column( - modifier = Modifier - .fillMaxSize() - .background( - brush = Brush.horizontalGradient( - colors = listOf( - MaterialTheme.colorScheme.surface, - MaterialTheme.colorScheme.surface.copy(alpha = 0.96f), - ), - ), - ) - .statusBarsPadding(), + shape = RoundedCornerShape(topStart = 24.dp, bottomStart = 24.dp), + color = MaterialTheme.colorScheme.surface, + tonalElevation = 4.dp, + shadowElevation = 24.dp, ) { - Row( + Column( modifier = Modifier - .fillMaxWidth() - .padding(start = 20.dp, end = 8.dp, top = 16.dp, bottom = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, + .fillMaxSize() + .background( + brush = Brush.horizontalGradient( + colors = listOf( + MaterialTheme.colorScheme.surface, + MaterialTheme.colorScheme.surface.copy(alpha = 0.96f), + ), + ), + ) + .statusBarsPadding(), ) { - Text( - text = stringResource(R.string.screen_effects), - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - ) - - IconButton(onClick = onDismiss) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = stringResource(R.string.screen_effects_close), - tint = MaterialTheme.colorScheme.onSurfaceVariant, + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 20.dp, end = 8.dp, top = 16.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(R.string.screen_effects), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold, ) + + IconButton(onClick = onDismiss) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = stringResource(R.string.screen_effects_close), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } } - } - HorizontalDivider( - modifier = Modifier.padding(horizontal = 16.dp), - color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f), - ) + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f), + ) - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .focusGroup() - .onPreviewKeyEvent { keyEvent -> - if (keyEvent.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) { - when (keyEvent.nativeKeyEvent.keyCode) { - KeyEvent.KEYCODE_BUTTON_B, - KeyEvent.KEYCODE_BACK -> { - onDismiss() - true + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .focusGroup() + .onPreviewKeyEvent { keyEvent -> + if (keyEvent.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) { + when (keyEvent.nativeKeyEvent.keyCode) { + KeyEvent.KEYCODE_BUTTON_B, + KeyEvent.KEYCODE_BACK, + -> { + onDismiss() + true + } + else -> false } - else -> false + } else { + false } - } else { - false } - } - .padding(vertical = 12.dp), - ) { - OptionSectionHeader(text = stringResource(R.string.screen_effects_color_adjustments)) - - ScreenEffectAdjustmentRow( - title = stringResource(R.string.screen_effects_brightness), - valueText = formatPercent(brightness), - progress = normalizedProgress(brightness, -100f, 100f), - onDecrease = { - brightness = (brightness - SCREEN_EFFECT_PERCENT_STEP).coerceIn(-100f, 100f) - }, - onIncrease = { - brightness = (brightness + SCREEN_EFFECT_PERCENT_STEP).coerceIn(-100f, 100f) - }, - focusRequester = firstItemFocusRequester, - ) - ScreenEffectAdjustmentRow( - title = stringResource(R.string.screen_effects_contrast), - valueText = formatPercent(contrast), - progress = normalizedProgress(contrast, -100f, 100f), - onDecrease = { - contrast = (contrast - SCREEN_EFFECT_PERCENT_STEP).coerceIn(-100f, 100f) - }, - onIncrease = { - contrast = (contrast + SCREEN_EFFECT_PERCENT_STEP).coerceIn(-100f, 100f) - }, - ) - ScreenEffectAdjustmentRow( - title = stringResource(R.string.screen_effects_gamma), - valueText = String.format("%.2fx", gamma), - progress = normalizedProgress(gamma, 0.5f, 2.5f), - onDecrease = { - gamma = (gamma - SCREEN_EFFECT_GAMMA_STEP).coerceIn(0.5f, 2.5f) - }, - onIncrease = { - gamma = (gamma + SCREEN_EFFECT_GAMMA_STEP).coerceIn(0.5f, 2.5f) - }, - ) + .padding(vertical = 12.dp), + ) { + OptionSectionHeader(text = stringResource(R.string.screen_effects_color_adjustments)) + + ScreenEffectAdjustmentRow( + title = stringResource(R.string.screen_effects_brightness), + valueText = formatPercent(brightness), + progress = normalizedProgress(brightness, -100f, 100f), + onDecrease = { + brightness = (brightness - SCREEN_EFFECT_PERCENT_STEP).coerceIn(-100f, 100f) + }, + onIncrease = { + brightness = (brightness + SCREEN_EFFECT_PERCENT_STEP).coerceIn(-100f, 100f) + }, + focusRequester = firstItemFocusRequester, + ) + ScreenEffectAdjustmentRow( + title = stringResource(R.string.screen_effects_contrast), + valueText = formatPercent(contrast), + progress = normalizedProgress(contrast, -100f, 100f), + onDecrease = { + contrast = (contrast - SCREEN_EFFECT_PERCENT_STEP).coerceIn(-100f, 100f) + }, + onIncrease = { + contrast = (contrast + SCREEN_EFFECT_PERCENT_STEP).coerceIn(-100f, 100f) + }, + ) + ScreenEffectAdjustmentRow( + title = stringResource(R.string.screen_effects_gamma), + valueText = String.format("%.2fx", gamma), + progress = normalizedProgress(gamma, 0.5f, 2.5f), + onDecrease = { + gamma = (gamma - SCREEN_EFFECT_GAMMA_STEP).coerceIn(0.5f, 2.5f) + }, + onIncrease = { + gamma = (gamma + SCREEN_EFFECT_GAMMA_STEP).coerceIn(0.5f, 2.5f) + }, + ) - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(20.dp)) - OptionSectionHeader(text = stringResource(R.string.screen_effects_shader_toggles)) + OptionSectionHeader(text = stringResource(R.string.screen_effects_shader_toggles)) - ScreenEffectToggleRow( - title = stringResource(R.string.screen_effects_toon), - subtitle = stringResource(R.string.screen_effects_toon_description), - enabled = enableToon, - onToggle = { enableToon = !enableToon }, - ) - ScreenEffectToggleRow( - title = stringResource(R.string.screen_effects_fxaa), - subtitle = stringResource(R.string.screen_effects_fxaa_description), - enabled = enableFXAA, - onToggle = { enableFXAA = !enableFXAA }, - ) - ScreenEffectToggleRow( - title = stringResource(R.string.screen_effects_vivid), - subtitle = stringResource(R.string.screen_effects_vivid_description), - enabled = enableVivid, - onToggle = { enableVivid = !enableVivid }, - ) - ScreenEffectToggleRow( - title = stringResource(R.string.screen_effects_crt), - subtitle = stringResource(R.string.screen_effects_crt_description), - enabled = enableCRT, - onToggle = { enableCRT = !enableCRT }, - ) - ScreenEffectToggleRow( - title = stringResource(R.string.screen_effects_ntsc), - subtitle = stringResource(R.string.screen_effects_ntsc_description), - enabled = enableNTSC, - onToggle = { enableNTSC = !enableNTSC }, - ) + ScreenEffectToggleRow( + title = stringResource(R.string.screen_effects_toon), + subtitle = stringResource(R.string.screen_effects_toon_description), + enabled = enableToon, + onToggle = { enableToon = !enableToon }, + ) + ScreenEffectToggleRow( + title = stringResource(R.string.screen_effects_fxaa), + subtitle = stringResource(R.string.screen_effects_fxaa_description), + enabled = enableFXAA, + onToggle = { enableFXAA = !enableFXAA }, + ) + ScreenEffectToggleRow( + title = stringResource(R.string.screen_effects_vivid), + subtitle = stringResource(R.string.screen_effects_vivid_description), + enabled = enableVivid, + onToggle = { enableVivid = !enableVivid }, + ) + ScreenEffectToggleRow( + title = stringResource(R.string.screen_effects_crt), + subtitle = stringResource(R.string.screen_effects_crt_description), + enabled = enableCRT, + onToggle = { enableCRT = !enableCRT }, + ) + ScreenEffectToggleRow( + title = stringResource(R.string.screen_effects_ntsc), + subtitle = stringResource(R.string.screen_effects_ntsc_description), + enabled = enableNTSC, + onToggle = { enableNTSC = !enableNTSC }, + ) - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(20.dp)) - ScreenEffectActionRow( - title = stringResource(R.string.screen_effects_reset), - icon = Icons.Default.RestartAlt, - accentColor = PluviaTheme.colors.accentPurple, - onClick = ::resetEffects, - ) + ScreenEffectActionRow( + title = stringResource(R.string.screen_effects_reset), + icon = Icons.Default.RestartAlt, + accentColor = PluviaTheme.colors.accentPurple, + onClick = ::resetEffects, + ) - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(24.dp)) + } } } } } } -} @Composable private fun ScreenEffectAdjustmentRow( @@ -630,7 +630,7 @@ private fun ScreenEffectAdjustmentRow( Modifier.focusRequester(focusRequester) } else { Modifier - } + }, ) .onFocusChanged { if (!it.isFocused) { @@ -1008,5 +1008,5 @@ private fun normalizedProgress( private fun formatPercent(value: Float): String { val rounded = value.toInt() - return if (rounded > 0) "+${rounded}%" else "${rounded}%" + return if (rounded > 0) "+$rounded%" else "$rounded%" } diff --git a/app/src/main/java/app/gamenative/ui/component/dialog/ScreenEffectDialog.kt b/app/src/main/java/app/gamenative/ui/component/dialog/ScreenEffectDialog.kt index 700e98ba6d..7b96f4fd92 100644 --- a/app/src/main/java/app/gamenative/ui/component/dialog/ScreenEffectDialog.kt +++ b/app/src/main/java/app/gamenative/ui/component/dialog/ScreenEffectDialog.kt @@ -47,12 +47,12 @@ import app.gamenative.ui.util.loadScreenEffectsConfig import app.gamenative.ui.util.persistScreenEffectsConfig import com.alorma.compose.settings.ui.SettingsSwitch import com.winlator.container.Container -import com.winlator.renderer.GLRenderer +import com.winlator.renderer.VulkanRenderer import kotlinx.coroutines.delay @Composable fun ScreenEffectDialog( - renderer: GLRenderer, + renderer: VulkanRenderer, onDismiss: () -> Unit, container: Container? = null, ) { @@ -293,5 +293,5 @@ private fun ScreenEffectSlider( private fun formatPercent(value: Float): String { val rounded = value.toInt() - return if (rounded > 0) "+${rounded}%" else "${rounded}%" + return if (rounded > 0) "+$rounded%" else "$rounded%" } diff --git a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt index c48bea0562..5969b23d4b 100644 --- a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt @@ -2173,6 +2173,7 @@ fun XServerScreen( frameRating = FrameRating(context) frameRating?.setVisibility(View.GONE) xServerView.renderer.setFrameRating(frameRating) + shouldTrackDisplayedFrames.set(true) if (isPerformanceHudEnabled) { frameLayout.post { diff --git a/app/src/main/java/app/gamenative/ui/util/ScreenEffectsConfig.kt b/app/src/main/java/app/gamenative/ui/util/ScreenEffectsConfig.kt index a959a2d809..440b8a0e11 100644 --- a/app/src/main/java/app/gamenative/ui/util/ScreenEffectsConfig.kt +++ b/app/src/main/java/app/gamenative/ui/util/ScreenEffectsConfig.kt @@ -1,18 +1,7 @@ package app.gamenative.ui.util import com.winlator.container.Container -import com.winlator.renderer.GLRenderer -import com.winlator.renderer.effects.ColorEffect -import com.winlator.renderer.effects.CRTEffect -import com.winlator.renderer.effects.Effect -import com.winlator.renderer.effects.FSR1EasuEffect -import com.winlator.renderer.effects.FSR1RcasEffect -import com.winlator.renderer.effects.FXAAEffect -import com.winlator.renderer.effects.NTSCCombinedEffect -import com.winlator.renderer.effects.ScalingModeEffect -import com.winlator.renderer.effects.ToonEffect -import com.winlator.renderer.effects.VividEffect -import kotlin.math.abs +import com.winlator.renderer.VulkanRenderer data class ScreenEffectsConfig( val brightness: Float = 0f, @@ -60,7 +49,8 @@ fun loadScreenEffectsConfig(container: Container?): ScreenEffectsConfig { contrast = container.getExtra(ScreenEffectsConfig.KEY_CONTRAST)?.toFloatOrNull() ?: 0f, gamma = container.getExtra(ScreenEffectsConfig.KEY_GAMMA)?.toFloatOrNull() ?: 1f, scalingMode = container.getExtra(ScreenEffectsConfig.KEY_SCALING_MODE)?.toIntOrNull() ?: ScreenEffectsConfig.SCALING_MODE_NONE, - fsrSharpnessLevel = container.getExtra(ScreenEffectsConfig.KEY_FSR_SHARPNESS)?.toIntOrNull() ?: ScreenEffectsConfig.FSR_DEFAULT_LEVEL, + fsrSharpnessLevel = + container.getExtra(ScreenEffectsConfig.KEY_FSR_SHARPNESS)?.toIntOrNull() ?: ScreenEffectsConfig.FSR_DEFAULT_LEVEL, enableToon = container.getExtra(ScreenEffectsConfig.KEY_ENABLE_TOON)?.toBooleanStrictOrNull() ?: false, enableFXAA = container.getExtra(ScreenEffectsConfig.KEY_ENABLE_FXAA)?.toBooleanStrictOrNull() ?: false, enableVivid = container.getExtra(ScreenEffectsConfig.KEY_ENABLE_VIVID)?.toBooleanStrictOrNull() ?: false, @@ -98,56 +88,32 @@ fun fsrQuickMenuLevelToStops(level: Int): Float { } } -fun applyScreenEffectsConfig(renderer: GLRenderer, config: ScreenEffectsConfig) { - val composer = renderer.getEffectComposer() - val effects = mutableListOf() +/** + * Applies post-processing config to the Vulkan renderer. + * + * Single-pass effects available in the SPIR-V fragment shader: + * FSR (+ DLS sharpening), CRT, HDR (Vivid), Natural. + * Multi-pass effects (FXAA, Toon, NTSC) require additional render passes — deferred. + * Brightness / contrast / gamma are applied as a post-effect colour adjustment. + */ +fun applyScreenEffectsConfig(renderer: VulkanRenderer, config: ScreenEffectsConfig) { + // Filter mode: nearest-neighbour vs linear interpolation. + val filterMode = if (config.scalingMode == ScreenEffectsConfig.SCALING_MODE_NEAREST) 1 else 0 + renderer.setFilterMode(filterMode) - when (config.scalingMode) { - ScreenEffectsConfig.SCALING_MODE_FSR, ScreenEffectsConfig.SCALING_MODE_FSR_ASPECT -> { - val easuEffect = composer.getEffect(FSR1EasuEffect::class.java) ?: FSR1EasuEffect() - easuEffect.setPreserveAspect(config.scalingMode == ScreenEffectsConfig.SCALING_MODE_FSR_ASPECT) - effects += easuEffect - val rcasEffect = composer.getEffect(FSR1RcasEffect::class.java) ?: FSR1RcasEffect() - rcasEffect.sharpnessStops = fsrQuickMenuLevelToStops(config.fsrSharpnessLevel) - effects += rcasEffect - } - ScreenEffectsConfig.SCALING_MODE_NONE -> Unit - else -> { - val scalingEffect = composer.getEffect(ScalingModeEffect::class.java) ?: ScalingModeEffect() - scalingEffect.mode = when (config.scalingMode) { - ScreenEffectsConfig.SCALING_MODE_NEAREST -> ScalingModeEffect.Mode.NEAREST - ScreenEffectsConfig.SCALING_MODE_FILL -> ScalingModeEffect.Mode.FILL - ScreenEffectsConfig.SCALING_MODE_STRETCH -> ScalingModeEffect.Mode.STRETCH - else -> ScalingModeEffect.Mode.LINEAR - } - effects += scalingEffect - } - } + // Colour adjustments are applied in the fragment shader regardless of effect. + renderer.setColorAdjustment(config.brightness, config.contrast, config.gamma) - if (abs(config.brightness) > 0.001f || abs(config.contrast) > 0.001f || abs(config.gamma - 1.0f) > 0.001f) { - val colorEffect = ColorEffect().apply { - brightness = config.brightness / 100f - contrast = config.contrast / 100f - gamma = config.gamma + // Effect selection: priority order matches the original EffectComposer chain. + val sharpness = fsrQuickMenuLevelToStops(config.fsrSharpnessLevel) + when { + config.scalingMode == ScreenEffectsConfig.SCALING_MODE_FSR || + config.scalingMode == ScreenEffectsConfig.SCALING_MODE_FSR_ASPECT -> { + renderer.setEffect(VulkanRenderer.EFFECT_FSR, sharpness) } - effects += colorEffect - } - - if (config.enableToon) { - effects += composer.getEffect(ToonEffect::class.java) ?: ToonEffect() - } - if (config.enableFXAA) { - effects += composer.getEffect(FXAAEffect::class.java) ?: FXAAEffect() - } - if (config.enableVivid) { - effects += composer.getEffect(VividEffect::class.java) ?: VividEffect() + config.enableCRT -> renderer.setEffect(VulkanRenderer.EFFECT_CRT, sharpness) + config.enableVivid -> renderer.setEffect(VulkanRenderer.EFFECT_HDR, sharpness) + // FXAA, Toon, NTSC need multi-pass — fall back to no post-processing for now. + else -> renderer.setEffect(VulkanRenderer.EFFECT_NONE, sharpness) } - if (config.enableCRT) { - effects += composer.getEffect(CRTEffect::class.java) ?: CRTEffect() - } - if (config.enableNTSC) { - effects += composer.getEffect(NTSCCombinedEffect::class.java) ?: NTSCCombinedEffect() - } - - composer.setEffects(effects) } diff --git a/app/src/main/java/com/winlator/renderer/VulkanRenderer.java b/app/src/main/java/com/winlator/renderer/VulkanRenderer.java new file mode 100644 index 0000000000..627078b849 --- /dev/null +++ b/app/src/main/java/com/winlator/renderer/VulkanRenderer.java @@ -0,0 +1,892 @@ +package com.winlator.renderer; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.view.Surface; + +import app.gamenative.R; +import com.winlator.xserver.Bitmask; +import com.winlator.xserver.Cursor; +import com.winlator.xserver.Drawable; +import com.winlator.xserver.Pointer; +import com.winlator.xserver.Window; +import com.winlator.xserver.WindowAttributes; +import com.winlator.xserver.WindowManager; +import com.winlator.xserver.XLock; +import com.winlator.xserver.XServer; +import com.winlator.widget.XServerView; + +import java.util.ArrayList; + +public class VulkanRenderer implements WindowManager.OnWindowModificationListener, + Pointer.OnPointerMotionListener { + + static { System.loadLibrary("vulkan_renderer"); } + + // Effect IDs that match the constants in window.frag / VulkanRendererContext. + public static final int EFFECT_NONE = 0; + public static final int EFFECT_FSR = 1; + public static final int EFFECT_DLS = 2; + public static final int EFFECT_CRT = 3; + public static final int EFFECT_HDR = 4; + public static final int EFFECT_NATURAL = 5; + + public final XServerView xServerView; + private final XServer xServer; + + // Long that holds the pointer to the native VulkanRendererContext on the heap. + // 0 means the context has not been initialised yet. + private long nativeHandle = 0; + private final Object lock = new Object(); + + public final ViewTransformation viewTransformation = new ViewTransformation(); + private boolean fullscreen = false; + private float magnifierZoom = 1.0f; + private boolean screenOffsetYRelativeToCursor = false; + public int surfaceWidth; + public int surfaceHeight; + + // WM class whose windows should be hidden from the compositor (e.g. explorer.exe). + private String[] unviewableWMClasses = null; + + // When set, windows matching this WM class that are smaller than the screen but large + // enough to be a game viewport are promoted to fullscreen in the render list. + // This is a GameNative addition not present in the upstream Ludashi renderer. + public String forceFullscreenWMClass = null; + + private boolean cursorVisible = false; + private boolean nativeMode = false; + private String driverPath = null; + private java.util.concurrent.ExecutorService initExecutor = null; + private volatile boolean initComplete = false; + private String driverLibraryName = null; + private String nativeLibDir = null; + private Drawable rootCursorDrawable; + private Cursor lastCursor = null; + + + // Called once after each composited frame — wired from XServerScreen to update the HUD. + private Runnable onFrameRenderedListener = null; + + private volatile ArrayList renderableWindows = new ArrayList<>(); + private static final java.util.concurrent.atomic.AtomicLong ID_GEN = + new java.util.concurrent.atomic.AtomicLong(1); + private final java.util.WeakHashMap drawableIds = + new java.util.WeakHashMap<>(); + private final java.util.concurrent.atomic.AtomicBoolean scenePending = + new java.util.concurrent.atomic.AtomicBoolean(false); + + // SurfaceControl children for direct-scanout / native mode (API 29+). + private android.view.SurfaceControl scanoutGameSC; + private android.view.SurfaceControl scanoutCursorSC; + private android.view.Surface scanoutGameSurface; + private android.view.Surface scanoutCursorSurface; + + // ------------------------------------------------------------------------- + // JNI declarations + // All functions are implemented in vulkan_jni.cpp. + // ------------------------------------------------------------------------- + private native long nativeInit(Surface surface, int screenWidth, int screenHeight, + String driverPath, String libraryName, String nativeLibDir); + private native void nativeResize(long handle, int width, int height); + private native void nativeDestroy(long handle); + private native void nativeUpdateWindowContent(long handle, long id, + java.nio.ByteBuffer pixels, short width, short height, short stride, int x, int y); + private native void nativeUpdateWindowContentAHB(long handle, long id, long ahbPtr, + short width, short height, int x, int y); + private native void nativeSetTransform(long handle, float ox, float oy, float sx, float sy); + private native void nativeSetPointerPos(long handle, short x, short y); + private native void nativeSetCursorVisible(long handle, boolean visible); + private native void nativeUpdateCursorImage(long handle, java.nio.ByteBuffer pixels, + short width, short height, short hotX, short hotY); + private native void nativeSetRenderList(long handle, long[] ids, int[] xs, int[] ys, int count); + private native void nativeRemoveWindow(long handle, long id); + private native void nativeInitScanout(long handle); + private native void nativeDetachSurface(long handle); + private native boolean nativeReattachSurface(long handle, android.view.Surface surface); + private native void nativeDestroyScanout(long handle); + private native void nativeScanoutSetBuffer(long handle, long ahbPtr, int x, int y, int w, int h, int fenceFd); + private native void nativeScanoutSetCursorImage(long handle, + java.nio.ByteBuffer pixels, short w, short h, short stride); + private native void nativeScanoutSetCursorPos(long handle, short x, short y, short hotX, short hotY); + private native boolean nativeIsScanoutActive(long handle); + private native boolean nativeIsGameFrameDelivered(long handle); + private native void nativeSetScanoutWindow(long handle, + android.view.Surface game, android.view.Surface cursor); + private native void nativeScanoutSetDst(long handle, int x, int y, int w, int h); + private native void nativeSetVerboseLog(long handle, boolean v); + private native void nativeDumpRendererInfo(long handle); + private native void nativeSetFilterMode(long handle, int mode); + private native void nativeSetSwapRB(long handle, boolean enabled); + private native void nativeSetPresentMode(long handle, int mode); + private native void nativeSetEffect(long handle, int effectId, float sharpness); + private native void nativeSetColorAdjustment(long handle, float brightness, float contrast, float gamma); + + // Pending native state — held until nativeHandle is live so the first + // nativeInit call can apply them immediately. + private int pendingPresentMode = 1; // 1 = MAILBOX + private int pendingFilterMode = 0; + private boolean pendingSwapRB = false; + private int pendingEffectId = EFFECT_NONE; + private float pendingSharpness = 1.0f; + private float pendingBrightness = 0.0f; + private float pendingContrast = 0.0f; + private float pendingGamma = 1.0f; + + private static volatile boolean gpuImageChecked = false; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + public VulkanRenderer(XServerView xServerView, XServer xServer) { + this.xServerView = xServerView; + this.xServer = xServer; + rootCursorDrawable = createRootCursorDrawable(); + xServer.windowManager.addOnWindowModificationListener(this); + xServer.pointer.addOnPointerMotionListener(this); + } + + private Drawable createRootCursorDrawable() { + try { + Context context = xServerView.getContext(); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + Bitmap bitmap = BitmapFactory.decodeResource( + context.getResources(), R.drawable.cursor, options); + return Drawable.fromBitmap(bitmap); + } catch (Exception e) { + return null; + } + } + + // ------------------------------------------------------------------------- + // Drawable → stable 64-bit ID mapping + // ------------------------------------------------------------------------- + + /** Returns a stable numeric ID for this Drawable, creating one if needed. */ + private long did(Drawable d) { + return drawableIds.computeIfAbsent(d, k -> ID_GEN.getAndIncrement()); + } + + // ------------------------------------------------------------------------- + // Scene update — called whenever the X11 window tree changes + // ------------------------------------------------------------------------- + + /** + * Queues a scene rebuild on the event executor. The atomic flag prevents + * redundant rebuilds when multiple window modifications arrive in a burst. + */ + public void queueSceneUpdate() { + if (scenePending.compareAndSet(false, true)) { + xServerView.queueEvent(() -> { + scenePending.set(false); + updateScene(); + }); + } + } + + public void updateScene() { + ArrayList newList = new ArrayList<>(); + try (XLock xl = xServer.lock(XServer.Lockable.WINDOW_MANAGER, XServer.Lockable.DRAWABLE_MANAGER)) { + collectWindows(newList, xServer.windowManager.rootWindow, + xServer.windowManager.rootWindow.getX(), + xServer.windowManager.rootWindow.getY()); + } + synchronized (lock) { + renderableWindows = newList; + pushRenderList(newList); + } + } + + /** + * Recursively walks the X11 window tree, collecting visible windows into the + * render list in paint order (back-to-front). + * + *

GameNative addition: if {@link #forceFullscreenWMClass} is set, sub-screen-sized + * windows whose WM class matches are promoted to fullscreen so the game viewport fills + * the display regardless of X11 desktop chrome around it. + */ + private void collectWindows(ArrayList list, Window window, int x, int y) { + if (!window.attributes.isMapped()) return; + if (window != xServer.windowManager.rootWindow) { + boolean viewable = true; + if (unviewableWMClasses != null) { + String wc = window.getClassName(); + for (String cls : unviewableWMClasses) { + if (wc.contains(cls)) { + if (window.attributes.isEnabled()) window.disableAllDescendants(); + viewable = false; + break; + } + } + } + if (viewable) { + boolean forceFullscreen = false; + if (forceFullscreenWMClass != null) { + short w = window.getWidth(); + short h = window.getHeight(); + if (w >= 320 && h >= 200 + && w < xServer.screenInfo.width + && h < xServer.screenInfo.height) { + Window parent = window.getParent(); + boolean parentHasClass = parent.getClassName() + .contains(forceFullscreenWMClass); + boolean hasClass = window.getClassName() + .contains(forceFullscreenWMClass); + if (hasClass) { + forceFullscreen = !parentHasClass && window.getChildCount() == 0; + } else { + short borderX = (short)(parent.getWidth() - w); + short borderY = (short)(parent.getHeight() - h); + if (parent.getChildCount() == 1 + && borderX > 0 && borderY > 0 && borderX <= 12) { + forceFullscreen = true; + // Remove the wrapping parent from the list to avoid overdraw. + list.removeIf(rw -> rw.content == parent.getContent()); + } + } + } + } + list.add(new RenderableWindow(window.getContent(), x, y, forceFullscreen)); + } + } + for (Window child : window.getChildren()) { + collectWindows(list, child, child.getX() + x, child.getY() + y); + } + } + + /** + * Converts the Java render list into the three parallel arrays expected by the native + * side and submits them. Windows that are fully offscreen are still included so the + * native compositor can decide whether to skip them. + * + *

The "start index" skips over desktop background windows so that the compositor + * does not waste time drawing layers that will be completely covered. + */ + private void pushRenderList(ArrayList list) { + if (nativeHandle == 0) return; + int screenW = xServer.screenInfo.width; + int screenH = xServer.screenInfo.height; + + // Find the back-most window that fills the whole screen — no need to draw what + // is fully behind it. + int start = 0; + for (int i = list.size() - 1; i >= 0; i--) { + RenderableWindow rw = list.get(i); + if (rw.content != null + && rw.content.width >= screenW + && rw.content.height >= screenH) { + start = i; + break; + } + } + + if (nativeMode) { + // In native/scanout mode the compositor still renders all windows (GameNative + // does not implement direct AHB scanout bypass at the Drawable level). + int n = list.size() - start; + long[] ids = new long[n]; int[] xs = new int[n]; int[] ys = new int[n]; + for (int i = 0; i < n; i++) { + ids[i] = did(list.get(start + i).content); + xs[i] = list.get(start + i).rootX; + ys[i] = list.get(start + i).rootY; + } + nativeSetRenderList(nativeHandle, ids, xs, ys, n); + return; + } + + int n = list.size() - start; + long[] ids = new long[n]; int[] xs = new int[n]; int[] ys = new int[n]; + for (int i = 0; i < n; i++) { + RenderableWindow rw = list.get(start + i); + ids[i] = did(rw.content); + xs[i] = rw.rootX; + ys[i] = rw.rootY; + } + nativeSetRenderList(nativeHandle, ids, xs, ys, n); + } + + // ------------------------------------------------------------------------- + // Surface lifecycle — called by XServerView's SurfaceHolder.Callback + // ------------------------------------------------------------------------- + + /** + * Called when a rendering surface becomes available (app start, resume after minimise). + * + *

Initialisation is moved off the main thread to an {@code initExecutor} so the UI + * never blocks. Once the native handle is ready, the first scene push is queued. + */ + public void onSurfaceCreated(Surface surface) { + if (!gpuImageChecked) { + GPUImage.checkIsSupported(); + gpuImageChecked = true; + } + if (initExecutor != null) { + initExecutor.shutdownNow(); + try { + initExecutor.awaitTermination(3, java.util.concurrent.TimeUnit.SECONDS); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } + initExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(); + initExecutor.execute(() -> { + synchronized (lock) { + if (nativeHandle != 0) { + // Surface was recreated (e.g. screen rotation) — try to reattach cheaply. + boolean ok = nativeReattachSurface(nativeHandle, surface); + if (!ok) { + nativeDestroy(nativeHandle); + nativeHandle = 0; + } else { + initComplete = true; + xServerView.queueEvent(this::updateScene); + return; + } + } + nativeHandle = nativeInit(surface, + xServer.screenInfo.width, xServer.screenInfo.height, + driverPath, driverLibraryName, nativeLibDir); + if (nativeHandle != 0) { + // Apply any settings that arrived before init completed. + nativeSetPresentMode(nativeHandle, pendingPresentMode); + nativeSetFilterMode(nativeHandle, pendingFilterMode); + nativeSetSwapRB(nativeHandle, pendingSwapRB); + nativeSetEffect(nativeHandle, pendingEffectId, pendingSharpness); + nativeSetColorAdjustment(nativeHandle, pendingBrightness, pendingContrast, pendingGamma); + updateTransform(); + nativeSetCursorVisible(nativeHandle, cursorVisible); + if (nativeMode) { + xServerView.post(() -> { + releaseScanoutSurfaces(); + if (android.os.Build.VERSION.SDK_INT >= 30) { + try { + android.view.SurfaceControl xsc = xServerView.getSurfaceControl(); + scanoutGameSC = new android.view.SurfaceControl.Builder() + .setParent(xsc).setName("winlator_game") + .setOpaque(true).build(); + scanoutGameSurface = new android.view.Surface(scanoutGameSC); + scanoutCursorSC = new android.view.SurfaceControl.Builder() + .setParent(xsc).setName("winlator_cursor") + .setFormat(1).build(); + scanoutCursorSurface = new android.view.Surface(scanoutCursorSC); + new android.view.SurfaceControl.Transaction() + .setLayer(scanoutGameSC, 1) + .setLayer(scanoutCursorSC, 2) + .setVisibility(scanoutGameSC, true) + .setVisibility(scanoutCursorSC, true) + .apply(); + synchronized (lock) { + if (nativeHandle != 0) { + nativeSetScanoutWindow(nativeHandle, + scanoutGameSurface, scanoutCursorSurface); + updateTransform(); + } + } + } catch (Exception e) { + android.util.Log.w("VulkanRenderer", + "SC recreate failed on surface restore: " + e); + synchronized (lock) { + if (nativeHandle != 0) nativeInitScanout(nativeHandle); + } + } + } else { + synchronized (lock) { + if (nativeHandle != 0) nativeInitScanout(nativeHandle); + } + } + }); + } + } + } + synchronized (lock) { + if (nativeHandle != 0) { + nativeSetVerboseLog(nativeHandle, true); + nativeDumpRendererInfo(nativeHandle); + } + } + initComplete = true; + xServerView.queueEvent(this::updateScene); + }); + } + + /** + * Called when the surface dimensions change. Rebuilds the Vulkan swapchain and + * updates the viewport-to-screen-coordinates transform. + */ + public void onSurfaceChanged(int width, int height) { + surfaceWidth = width; + surfaceHeight = height; + viewTransformation.update(width, height, + xServer.screenInfo.width, xServer.screenInfo.height); + synchronized (lock) { + if (nativeHandle != 0) { + nativeResize(nativeHandle, width, height); + updateTransform(); + } + } + } + + /** + * Called when the surface is destroyed (app is minimised or paused). + * Detaches the surface from the native renderer without destroying its resources + * so they can be reattached cheaply when the surface is recreated. + */ + public void onSurfaceDestroyed() { + initComplete = false; + if (initExecutor != null) { + initExecutor.shutdownNow(); + try { + initExecutor.awaitTermination(3, java.util.concurrent.TimeUnit.SECONDS); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + initExecutor = null; + } + synchronized (lock) { + if (nativeHandle != 0) { + if (nativeMode) { + nativeDestroyScanout(nativeHandle); + nativeDestroy(nativeHandle); + nativeHandle = 0; + } else { + nativeDetachSurface(nativeHandle); + } + } + } + if (nativeMode) xServerView.post(this::releaseScanoutSurfaces); + } + + private void releaseScanoutSurfaces() { + if (scanoutGameSurface != null) { + scanoutGameSurface.release(); scanoutGameSurface = null; + } + if (scanoutCursorSurface != null) { + scanoutCursorSurface.release(); scanoutCursorSurface = null; + } + if (scanoutGameSC != null) { + scanoutGameSC.release(); scanoutGameSC = null; + } + if (scanoutCursorSC != null) { + scanoutCursorSC.release(); scanoutCursorSC = null; + } + } + + // ------------------------------------------------------------------------- + // Coordinate transform + // ------------------------------------------------------------------------- + + /** + * Pushes the current view-to-screen transform to the native renderer. + * Called whenever surface dimensions or the fullscreen flag change. + */ + private void updateTransform() { + if (nativeHandle == 0) return; + if (fullscreen) { + nativeSetTransform(nativeHandle, 0, 0, 1.0f, 1.0f); + viewTransformation.update(surfaceWidth, surfaceHeight, + xServer.screenInfo.width, xServer.screenInfo.height); + nativeScanoutSetDst(nativeHandle, + viewTransformation.viewOffsetX, + viewTransformation.viewOffsetY, + viewTransformation.viewWidth, + viewTransformation.viewHeight); + } else { + float py = 0; + if (screenOffsetYRelativeToCursor) { + short halfH = (short)(xServer.screenInfo.height / 2); + py = Math.max(0, Math.min(xServer.pointer.getY() - halfH / 2.0f, halfH)); + } + nativeSetTransform(nativeHandle, + viewTransformation.sceneOffsetX, + viewTransformation.sceneOffsetY - py, + viewTransformation.sceneScaleX, + viewTransformation.sceneScaleY); + nativeScanoutSetDst(nativeHandle, + viewTransformation.viewOffsetX, + viewTransformation.viewOffsetY, + viewTransformation.viewWidth, + viewTransformation.viewHeight); + } + } + + // ------------------------------------------------------------------------- + // Pixel buffer delivery — called by PresentExtension / DRI3Extension + // ------------------------------------------------------------------------- + + /** + * Delivers a completed game frame from the X11 Present extension directly to the + * Vulkan compositor, bypassing the scene rebuild path for lower latency. + * + *

If the Drawable has an AHardwareBuffer, the GPU handle is passed directly + * (zero-copy). Otherwise the CPU pixel buffer is uploaded via a staging copy. + */ + public void onUpdateWindowContentDirect(Window window, Drawable pixmap, + short xOff, short yOff) { + if (nativeHandle == 0 || pixmap == null) return; + Drawable targetDrawable = window.getContent(); + long targetId = did(targetDrawable); + int rx = window.getRootX() + xOff; + int ry = window.getRootY() + yOff; + synchronized (pixmap.renderLock) { + Texture texture = pixmap.getTexture(); + if (texture instanceof GPUImage) { + GPUImage g = (GPUImage) texture; + long ahbPtr = g.getHardwareBufferPtr(); + if (ahbPtr != 0) { + nativeUpdateWindowContentAHB(nativeHandle, targetId, ahbPtr, + pixmap.width, pixmap.height, rx, ry); + return; + } + java.nio.ByteBuffer vd = g.getVirtualData(); + if (vd != null) { + short s = g.getStride() > 0 ? g.getStride() : pixmap.width; + nativeUpdateWindowContent(nativeHandle, targetId, vd, + pixmap.width, pixmap.height, s, rx, ry); + return; + } + } + java.nio.ByteBuffer buf = pixmap.getData(); + if (buf == null) return; + nativeUpdateWindowContent(nativeHandle, targetId, buf, + pixmap.width, pixmap.height, pixmap.width, rx, ry); + } + notifyFrameRendered(); + } + + // ------------------------------------------------------------------------- + // WindowManager.OnWindowModificationListener + // ------------------------------------------------------------------------- + + @Override + public void onUpdateWindowContent(Window window) { + final long handle; + synchronized (lock) { handle = nativeHandle; } + if (handle == 0) return; + + Drawable drawable = window.getContent(); + if (drawable == null || !window.attributes.isMapped()) return; + if (unviewableWMClasses != null) { + String wc = window.getClassName(); + for (String cls : unviewableWMClasses) { + if (wc.contains(cls)) return; + } + } + int rx = window.getRootX(); + int ry = window.getRootY(); + long drawableId = did(drawable); + + synchronized (drawable.renderLock) { + if (drawable.getTexture() instanceof GPUImage) { + GPUImage g = (GPUImage) drawable.getTexture(); + long ahbPtr = g.getHardwareBufferPtr(); + if (ahbPtr != 0) { + nativeUpdateWindowContentAHB(handle, drawableId, ahbPtr, + drawable.width, drawable.height, rx, ry); + return; + } + java.nio.ByteBuffer vd = g.getVirtualData(); + if (vd != null) { + short s = g.getStride() > 0 ? g.getStride() : drawable.width; + nativeUpdateWindowContent(handle, drawableId, vd, + drawable.width, drawable.height, s, rx, ry); + return; + } + } + java.nio.ByteBuffer buf = drawable.getData(); + if (buf == null) return; + nativeUpdateWindowContent(handle, drawableId, buf, + drawable.width, drawable.height, drawable.width, rx, ry); + } + notifyFrameRendered(); + } + + @Override public void onMapWindow(Window window) { queueSceneUpdate(); } + + @Override + public void onUnmapWindow(Window window) { + final long id = did(window.getContent()); + xServerView.queueEvent(() -> { + synchronized (lock) { + if (nativeHandle != 0) nativeRemoveWindow(nativeHandle, id); + } + queueSceneUpdate(); + }); + } + + @Override public void onChangeWindowZOrder(Window window) { queueSceneUpdate(); } + + @Override + public void onUpdateWindowGeometry(Window window, boolean resized) { + queueSceneUpdate(); + } + + @Override + public void onUpdateWindowAttributes(Window window, Bitmask mask) { + if (mask.isSet(WindowAttributes.FLAG_CURSOR)) { + synchronized (lock) { + Window pw = xServer.inputDeviceManager.getPointWindow(); + if (pw == window) { + lastCursor = window.attributes.getCursor(); + sendCursorToNative(lastCursor); + } + } + } + } + + // ------------------------------------------------------------------------- + // Pointer.OnPointerMotionListener + // ------------------------------------------------------------------------- + + @Override + public void onPointerMove(short x, short y) { + synchronized (lock) { + if (nativeHandle == 0) return; + nativeSetPointerPos(nativeHandle, x, y); + Window pw = xServer.inputDeviceManager.getPointWindow(); + Cursor cursor = pw != null ? pw.attributes.getCursor() : null; + if (cursor != lastCursor) { + lastCursor = cursor; + sendCursorToNative(cursor); + } + if (nativeMode) { + short hotX = 0, hotY = 0; + if (cursor != null) { + hotX = (short)cursor.hotSpotX; + hotY = (short)cursor.hotSpotY; + } + nativeScanoutSetCursorPos(nativeHandle, x, y, hotX, hotY); + } + if (screenOffsetYRelativeToCursor) updateTransform(); + } + } + + // ------------------------------------------------------------------------- + // Cursor helpers + // ------------------------------------------------------------------------- + + private void sendCursorToNative(Cursor cursor) { + if (nativeHandle == 0) return; + Drawable cd; + short hotX = 0, hotY = 0; + boolean effVis = cursorVisible; + if (cursor != null) { + if (!cursor.isVisible()) effVis = false; + cd = cursor.cursorImage; + hotX = (short)cursor.hotSpotX; + hotY = (short)cursor.hotSpotY; + } else { + cd = rootCursorDrawable; + } + nativeSetCursorVisible(nativeHandle, effVis); + if (effVis && cd != null && cd.getData() != null) { + synchronized (cd.renderLock) { + java.nio.ByteBuffer buf = cd.getData(); + nativeUpdateCursorImage(nativeHandle, + buf, cd.width, cd.height, hotX, hotY); + if (nativeMode) { + nativeScanoutSetCursorImage(nativeHandle, buf, cd.width, cd.height, cd.width); + } + } + } + } + + // ------------------------------------------------------------------------- + // Frame notification + // ------------------------------------------------------------------------- + + /** Fires the frame-rendered callback. Safe to call from any thread. */ + private void notifyFrameRendered() { + Runnable listener = onFrameRenderedListener; + if (listener != null) listener.run(); + } + + // ------------------------------------------------------------------------- + // Public API used by XServerScreen / QuickMenu + // ------------------------------------------------------------------------- + + public void setCursorVisible(boolean visible) { + cursorVisible = visible; + synchronized (lock) { + if (nativeHandle != 0) { + nativeSetCursorVisible(nativeHandle, visible); + if (visible) sendCursorToNative(lastCursor); + } + } + } + + public boolean isCursorVisible() { return cursorVisible; } + + /** Registers a callback that fires once after each composited frame. */ + public void setOnFrameRenderedListener(Runnable listener) { + this.onFrameRenderedListener = listener; + } + + /** + * No-op stub for API compatibility. GLRenderer used a FrameRating view; + * VulkanRenderer uses {@link #setOnFrameRenderedListener} instead. + */ + public void setFrameRating(Object frameRating) { + // Frame-rate tracking is wired via setOnFrameRenderedListener in XServerScreen. + // This stub keeps binary compatibility with callers that haven't been updated. + } + + public void setUnviewableWMClasses(String... classes) { + this.unviewableWMClasses = classes; + } + + /** + * Updates only the visual cursor position without changing the XServer pointer state. + * Used in relative mouse mode where movement is forwarded to Wine separately. + */ + public void updateVisualCursorPosition(int x, int y) { + synchronized (lock) { + if (nativeHandle == 0) return; + nativeSetPointerPos(nativeHandle, (short) x, (short) y); + } + } + + public boolean isFullscreen() { return fullscreen; } + public void toggleFullscreen() { + fullscreen = !fullscreen; + synchronized (lock) { updateTransform(); } + xServerView.queueEvent(this::updateScene); + } + public void setScreenOffsetYRelativeToCursor(boolean b) { + screenOffsetYRelativeToCursor = b; + synchronized (lock) { updateTransform(); } + } + public boolean isScreenOffsetYRelativeToCursor() { return screenOffsetYRelativeToCursor; } + public void setMagnifierZoom(float zoom) { magnifierZoom = zoom; } + public float getMagnifierZoom() { return magnifierZoom; } + + // ------------------------------------------------------------------------- + // Effect / presentation settings — used by QuickMenu and ScreenEffectsConfig + // ------------------------------------------------------------------------- + + /** + * Sets the active post-processing effect and sharpness. + * {@code effectId} must be one of the {@code EFFECT_*} constants. + */ + public void setEffect(int effectId, float sharpness) { + pendingEffectId = Math.max(EFFECT_NONE, Math.min(EFFECT_NATURAL, effectId)); + pendingSharpness = sharpness; + synchronized (lock) { + if (nativeHandle != 0) + nativeSetEffect(nativeHandle, pendingEffectId, pendingSharpness); + } + } + + public int getEffectId() { return pendingEffectId; } + public float getSharpness() { return pendingSharpness; } + + /** 0 = FIFO (VSync), 1 = MAILBOX (low-latency). Default is MAILBOX. */ + public void setPresentMode(int mode) { + pendingPresentMode = mode; + synchronized (lock) { + if (nativeHandle != 0) nativeSetPresentMode(nativeHandle, mode); + } + } + + /** 0 = linear, 1 = nearest-neighbour. */ + public void setFilterMode(int mode) { + pendingFilterMode = mode; + synchronized (lock) { + if (nativeHandle != 0) nativeSetFilterMode(nativeHandle, mode); + } + } + + public void setSwapRB(boolean enabled) { + pendingSwapRB = enabled; + synchronized (lock) { + if (nativeHandle != 0) nativeSetSwapRB(nativeHandle, enabled); + } + } + + /** + * Sets per-frame color adjustments applied after all effects. + * {@code brightness} is an additive offset in [-1, 1]. + * {@code contrast} is a multiplier offset in [-1, 1] (0 = no change). + * {@code gamma} is the gamma exponent (1.0 = no change, > 1.0 = brighter midtones). + */ + public void setColorAdjustment(float brightness, float contrast, float gamma) { + pendingBrightness = brightness; + pendingContrast = contrast; + pendingGamma = gamma; + synchronized (lock) { + if (nativeHandle != 0) nativeSetColorAdjustment(nativeHandle, brightness, contrast, gamma); + } + } + + // ------------------------------------------------------------------------- + // Adrenotools / custom Turnip driver (Phase B — Adreno only) + // ------------------------------------------------------------------------- + + /** + * Sets the custom Vulkan driver path for adrenotools injection. + * This is a Phase B feature — calling it on non-Adreno hardware is a safe no-op + * because the native init falls back to the system Vulkan driver if adrenotools + * fails to load. + */ + public void setDriverInfo(String driverPath, String libraryName, String nativeLibDir) { + this.driverPath = driverPath; + this.driverLibraryName = libraryName; + this.nativeLibDir = nativeLibDir; + } + + // ------------------------------------------------------------------------- + // Native mode (direct scanout, API 29+) + // ------------------------------------------------------------------------- + + public void setNativeMode(boolean mode) { + if (this.nativeMode == mode) return; + this.nativeMode = mode; + if (mode) { + xServerView.post(() -> { + if (android.os.Build.VERSION.SDK_INT >= 30) { + try { + android.view.SurfaceControl xsc = xServerView.getSurfaceControl(); + scanoutGameSC = new android.view.SurfaceControl.Builder() + .setParent(xsc).setName("winlator_game").setOpaque(true).build(); + scanoutGameSurface = new android.view.Surface(scanoutGameSC); + scanoutCursorSC = new android.view.SurfaceControl.Builder() + .setParent(xsc).setName("winlator_cursor").setFormat(1).build(); + scanoutCursorSurface = new android.view.Surface(scanoutCursorSC); + new android.view.SurfaceControl.Transaction() + .setLayer(scanoutGameSC, 1) + .setLayer(scanoutCursorSC, 2) + .setVisibility(scanoutGameSC, true) + .setVisibility(scanoutCursorSC, true) + .apply(); + synchronized (lock) { + if (nativeHandle != 0) { + nativeSetScanoutWindow(nativeHandle, + scanoutGameSurface, scanoutCursorSurface); + updateTransform(); + } + } + } catch (Exception e) { + android.util.Log.w("VulkanRenderer", + "Sibling SC failed, using child SC: " + e); + synchronized (lock) { + if (nativeHandle != 0) nativeInitScanout(nativeHandle); + } + } + } else { + synchronized (lock) { + if (nativeHandle != 0) nativeInitScanout(nativeHandle); + } + } + }); + } else { + synchronized (lock) { + if (nativeHandle != 0) nativeDestroyScanout(nativeHandle); + } + xServerView.post(this::releaseScanoutSurfaces); + } + xServerView.queueEvent(this::updateScene); + } + + public boolean isNativeMode() { return nativeMode; } +} diff --git a/app/src/main/java/com/winlator/widget/XServerView.java b/app/src/main/java/com/winlator/widget/XServerView.java index 8ae90985a7..ab301bce76 100644 --- a/app/src/main/java/com/winlator/widget/XServerView.java +++ b/app/src/main/java/com/winlator/widget/XServerView.java @@ -3,9 +3,10 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Rect; -import android.opengl.GLSurfaceView; import android.util.Log; import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -14,44 +15,63 @@ import androidx.collection.MutableObjectList; import com.winlator.core.Callback; -import com.winlator.renderer.GLRenderer; +import com.winlator.renderer.VulkanRenderer; import com.winlator.xserver.XServer; import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @SuppressLint("ViewConstructor") -public class XServerView extends GLSurfaceView { - private final GLRenderer renderer; +public class XServerView extends SurfaceView implements SurfaceHolder.Callback { + private final VulkanRenderer renderer; // private final ArrayList> mouseEventCallbacks = new ArrayList<>(); private final XServer xServer; private int frameRateLimit = 0; + /** + * Single-threaded executor used for all event-queue tasks (scene updates, window + * modifications, etc.) that must not block the UI thread. Replaces the thread + * that GLSurfaceView used to manage automatically. + */ + private final ExecutorService eventExecutor = Executors.newSingleThreadExecutor(); + public XServerView(Context context, XServer xServer) { super(context); setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - setEGLContextClientVersion(3); - setEGLConfigChooser(8, 8, 8, 8, 0, 0); - setPreserveEGLContextOnPause(true); this.xServer = xServer; - renderer = new GLRenderer(this, xServer); - setRenderer(renderer); - setRenderMode(RENDERMODE_WHEN_DIRTY); - - // setOnFocusChangeListener((view, gainFocus) -> { - // Log.d("XServerView", "onFocusChange: " + gainFocus + ", isMe: " + (view == this)); - // }); - // - // requestFocus(); + renderer = new VulkanRenderer(this, xServer); + getHolder().addCallback(this); + } + + // ------------------------------------------------------------------------- + // SurfaceHolder.Callback + // ------------------------------------------------------------------------- + + @Override + public void surfaceCreated(SurfaceHolder holder) { + renderer.onSurfaceCreated(holder.getSurface()); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + renderer.onSurfaceChanged(width, height); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + renderer.onSurfaceDestroyed(); } + + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + public XServer getxServer() { return xServer; } - // public void onRelease() { - // releasePointerCapture(); - // clearPointerEventListeners(); - // } - public GLRenderer getRenderer() { + public VulkanRenderer getRenderer() { return renderer; } @@ -59,72 +79,60 @@ public int getFrameRateLimit() { return frameRateLimit; } + /** + * Stores the desired frame-rate cap. The Vulkan compositor honours the cap in + * its render loop; no explicit requestRender() call is needed. + */ public void setFrameRateLimit(int frameRateLimit) { this.frameRateLimit = Math.max(0, frameRateLimit); - requestRender(); } - // public void addPointerEventListener(Callback listener) { - // mouseEventCallbacks.add(listener); - // } - // public void removePointerEventListener(Callback listener) { - // mouseEventCallbacks.remove(listener); - // } - // public void clearPointerEventListeners() { - // mouseEventCallbacks.clear(); - // } - // private void emitPointerEvent(MotionEvent event) { - // for (Callback listener : mouseEventCallbacks) { - // listener.call(event); - // } - // } - - // @Override - // public boolean onCapturedPointerEvent(MotionEvent event) { - // Log.d("XServerView", "onCapturedPointerEvent:\n\t" + event); - // emitPointerEvent(event); - // return true; - // } - - - // @Override - // public boolean dispatchGenericMotionEvent(MotionEvent event) { - // Log.d("XServerView", "dispatchGenericMotionEvent:\n\t" + event); - // return super.dispatchGenericMotionEvent(event); - // } - // - // @Override - // protected boolean dispatchGenericPointerEvent(MotionEvent event) { - // Log.d("XServerView", "dispatchGenericPointerEvent:\n\t" + event); - // return super.dispatchGenericPointerEvent(event); - // } - // - // @Override - // protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) { - // super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - // Log.d("XServerView", "Focus changed: " + gainFocus); - // } - // - // @Override - // public boolean dispatchCapturedPointerEvent(MotionEvent event) { - // Log.d("XServerView", "dispatchCapturedPointerEvent:\n\t" + event); - // emitPointerEvent(event); - // return super.dispatchCapturedPointerEvent(event); - // } - // - // @Override - // public void onPointerCaptureChange(boolean hasCapture) { - // super.onPointerCaptureChange(hasCapture); - // Log.d("XServerView", "onPointerCaptureChange: " + hasCapture); - // } - // - // @Override - // public void onWindowFocusChanged(boolean hasWindowFocus) { - // super.onWindowFocusChanged(hasWindowFocus); - // if (hasWindowFocus) { - // requestPointerCapture(); - // } else { - // releasePointerCapture(); - // } - // } + /** + * Runs r on the dedicated event executor. All X11 window-modification callbacks + * and scene-update tasks are queued here so the UI thread is never blocked. + * Replaces GLSurfaceView.queueEvent(). + */ + public void queueEvent(Runnable r) { + eventExecutor.execute(r); + } + + /** + * Legacy compatibility shim for GLRenderer which calls requestRender() on XServerView. + * The Vulkan compositor is event-driven and does not require explicit render requests; + * this method is a no-op. + */ + public void requestRender() { + // No-op: Vulkan render loop is driven by vkQueuePresentKHR / condition variables. + } + + /** + * Called when the hosting activity/fragment is paused. + * Surface lifecycle is handled via SurfaceHolder.Callback. + */ + public void onPause() { + // No-op: surface lifecycle is handled via SurfaceHolder.Callback. + } + + /** + * Called when the hosting activity/fragment is resumed. + * Surface lifecycle is handled via SurfaceHolder.Callback. + */ + public void onResume() { + // No-op: surface lifecycle is handled via SurfaceHolder.Callback. + } + + // Commented-out pointer capture / focus / motion event plumbing retained for + // reference. These require additional wiring (releasePointerCapture, etc.) and + // are left disabled until the input refactor is complete. + + // public void addPointerEventListener(Callback listener) { ... } + // public void removePointerEventListener(Callback listener) { ... } + // public void clearPointerEventListeners() { ... } + // @Override public boolean onCapturedPointerEvent(MotionEvent event) { ... } + // @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { ... } + // @Override protected boolean dispatchGenericPointerEvent(MotionEvent event) { ... } + // @Override protected void onFocusChanged(...) { ... } + // @Override public boolean dispatchCapturedPointerEvent(MotionEvent event) { ... } + // @Override public void onPointerCaptureChange(boolean hasCapture) { ... } + // @Override public void onWindowFocusChanged(boolean hasWindowFocus) { ... } } diff --git a/app/src/main/java/com/winlator/xenvironment/components/VirGLRendererComponent.java b/app/src/main/java/com/winlator/xenvironment/components/VirGLRendererComponent.java index 94e29d4138..526daeafc0 100644 --- a/app/src/main/java/com/winlator/xenvironment/components/VirGLRendererComponent.java +++ b/app/src/main/java/com/winlator/xenvironment/components/VirGLRendererComponent.java @@ -1,11 +1,12 @@ package com.winlator.xenvironment.components; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; import android.util.Log; import androidx.annotation.Keep; -import com.winlator.renderer.GLRenderer; -import com.winlator.renderer.Texture; +import com.winlator.renderer.VulkanRenderer; import com.winlator.xconnector.Client; import com.winlator.xconnector.ConnectionHandler; import com.winlator.xconnector.RequestHandler; @@ -18,11 +19,14 @@ import java.io.IOException; public class VirGLRendererComponent extends EnvironmentComponent implements ConnectionHandler, RequestHandler { + private static final String TAG = "VirGLRendererComponent"; private final XServer xServer; private final UnixSocketConfig socketConfig; private XConnectorEpoll connector; - private long sharedEGLContextPtr; - + // Detected once on the first flushFrontbuffer call. + // GL_BGRA_EXT requires GL_EXT_read_format_bgra; fall back to GL_RGBA + swapRB. + private volatile int readPixelsFormat = GLES11Ext.GL_BGRA; + private volatile boolean readFormatDetected = false; static { System.loadLibrary("virglrenderer"); } @@ -34,7 +38,7 @@ public VirGLRendererComponent(XServer xServer, UnixSocketConfig socketConfig) { @Override public void start() { - Log.d("VirGLRendererComponent", "Starting..."); + Log.d(TAG, "Starting..."); if (connector != null) return; connector = new XConnectorEpoll(socketConfig, this, this); connector.start(); @@ -42,7 +46,7 @@ public void start() { @Override public void stop() { - Log.d("VirGLRendererComponent", "Stopping..."); + Log.d(TAG, "Stopping..."); if (connector != null) { connector.stop(); connector = null; @@ -56,27 +60,7 @@ private void killConnection(int fd) { @Keep private long getSharedEGLContext() { - Log.d("VirGLRendererComponent", "Calling getSharedEGLContext"); - if (sharedEGLContextPtr != 0) return sharedEGLContextPtr; - final Thread thread = Thread.currentThread(); - try { - GLRenderer renderer = xServer.getRenderer(); - renderer.xServerView.queueEvent(() -> { - sharedEGLContextPtr = getCurrentEGLContextPtr(); - - synchronized(thread) { - thread.notify(); - } - }); - synchronized (thread) { - thread.wait(); - } - } - catch (Exception e) { - return 0; - } - Log.d("VirGLRendererComponent", "Finished getSharedEGLContext"); - return sharedEGLContextPtr; + return 0; } @Override @@ -87,45 +71,62 @@ public void handleConnectionShutdown(Client client) { @Override public void handleNewConnection(Client client) { - Log.d("VirGLRendererComponent", "Calling handleNewConnection"); - getSharedEGLContext(); + Log.d(TAG, "New connection fd=" + client.clientSocket.fd); long clientPtr = handleNewConnection(client.clientSocket.fd); client.setTag(clientPtr); - Log.d("VirGLRendererComponent", "Finished handleNewConnection"); } @Override public boolean handleRequest(Client client) throws IOException { - Log.d("VirGLRendererComponent", "Calling handleRequest"); long clientPtr = (long)client.getTag(); handleRequest(clientPtr); - Log.d("VirGLRendererComponent", "Finished handleRequest"); return true; } @Keep private void flushFrontbuffer(int drawableId, int framebuffer) { - Log.d("VirGLRendererComponent", "Calling flushFrontbuffer"); Drawable drawable = xServer.drawableManager.getDrawable(drawableId); if (drawable == null) return; synchronized (drawable.renderLock) { - drawable.setData(null); - Texture texture = drawable.getTexture(); - texture.copyFromFramebuffer(framebuffer, drawable.width, drawable.height); + java.nio.ByteBuffer buf = drawable.getData(); + if (buf == null) return; + buf.rewind(); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer); + GLES20.glReadPixels(0, 0, drawable.width, drawable.height, + readPixelsFormat, GLES20.GL_UNSIGNED_BYTE, buf); + + if (!readFormatDetected) { + readFormatDetected = true; + int err = GLES20.glGetError(); + if (err != GLES20.GL_NO_ERROR) { + Log.w(TAG, "GL_BGRA_EXT not supported (err=0x" + Integer.toHexString(err) + + "), falling back to GL_RGBA + swapRB"); + readPixelsFormat = GLES20.GL_RGBA; + // Re-read with the correct format for this first frame. + buf.rewind(); + GLES20.glReadPixels(0, 0, drawable.width, drawable.height, + GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); + // Tell the Vulkan compositor to swap R/B channels at composite time. + VulkanRenderer renderer = xServer.getRenderer(); + if (renderer != null) renderer.setSwapRB(true); + } else { + Log.d(TAG, "GL_BGRA_EXT read pixels OK"); + } + } + + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + buf.rewind(); } Runnable onDrawListener = drawable.getOnDrawListener(); if (onDrawListener != null) onDrawListener.run(); - Log.d("VirGLRendererComponent", "Finished flushFrontbuffer"); } private native long handleNewConnection(int fd); private native void handleRequest(long clientPtr); - private native long getCurrentEGLContextPtr(); - private native void destroyClient(long clientPtr); private native void destroyRenderer(long clientPtr); diff --git a/app/src/main/java/com/winlator/xserver/XServer.java b/app/src/main/java/com/winlator/xserver/XServer.java index 881fd5e05e..b23be9301c 100644 --- a/app/src/main/java/com/winlator/xserver/XServer.java +++ b/app/src/main/java/com/winlator/xserver/XServer.java @@ -5,7 +5,7 @@ import android.util.SparseArray; import com.winlator.math.Mathf; -import com.winlator.renderer.GLRenderer; +import com.winlator.renderer.VulkanRenderer; import com.winlator.winhandler.WinHandler; import com.winlator.xserver.extensions.BigReqExtension; import com.winlator.xserver.extensions.DRI3Extension; @@ -40,7 +40,7 @@ public enum Lockable {WINDOW_MANAGER, PIXMAP_MANAGER, DRAWABLE_MANAGER, GRAPHIC_ private boolean isGrabbed = false; private XClient grabbingClient = null; private SHMSegmentManager shmSegmentManager; - private GLRenderer renderer; + private VulkanRenderer renderer; private WinHandler winHandler; private final EnumMap locks = new EnumMap<>(Lockable.class); private boolean relativeMouseMovement = false; @@ -79,11 +79,11 @@ public void setSimulateTouchScreen(boolean simulateTouchScreen) { this.simulateTouchScreen = simulateTouchScreen; } - public GLRenderer getRenderer() { + public VulkanRenderer getRenderer() { return renderer; } - public void setRenderer(GLRenderer renderer) { + public void setRenderer(VulkanRenderer renderer) { this.renderer = renderer; }