mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 01:00:14 +02:00
- Eyes render under a VK_EXT_fragment_density_map: full rate around each eye's forward direction, 2x2 then 4x4 pixel blocks towards the edges ([vr] foveation = off|low|medium|high, default off). XR_FB_foveation cannot help here: the runtime's maps only shape passes drawing into its swapchain, and the eyes reach it through a copy. - aurora-main/patches/dawn/aurora_fdm.inc: Dawn enables the extension only on request and for dynamic rendering, flags every render pipeline, and chains an immutable RG8 map into any pass whose first color attachment is a view bound to one (ABI: include/aurora/dawn_fdm_abi.h). - android/Build-QuestDawn.ps1 builds the pinned Dawn revision with those patches for arm64 (dawn-build CI flags, protobuf off) into a cached package; Build-Quest.ps1 links it (-StockDawn opts out) and AuroraDawnProvider.cmake enables the ABI from its manifest. - lib/gfx/foveation.hpp generates the maps (32 px per texel, densities 255/127/63); an eye is foveated only when single_pass_eyes draws it in one render pass. Menus never are. - Live level from the headset panel's VR tab and the launcher; the launch decides whether the device has maps. debug.wiicompiled.foveation and debug.wiicompiled.fdm for A/B. - Tests: Foveation cases in gx_fifo_tests, mkw_vr_config_tests. Docs: OPENXR.md, quest-port.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
280 lines
13 KiB
C++
280 lines
13 KiB
C++
// Compiled inside VulkanBackend.cpp, using the pinned Dawn implementation (apply.py).
|
|
//
|
|
// Fragment density maps for Aurora's immersive eye render passes. Aurora uploads immutable RG8
|
|
// maps, binds each to the texture view its eye passes render through, and
|
|
// RecordBeginDynamicRenderPass chains the bound map into those passes. The maps are raw VkImages
|
|
// that Dawn's usage tracking never sees: they stay in FRAGMENT_DENSITY_MAP_OPTIMAL from their
|
|
// upload until the fenced deleter frees them.
|
|
#define AURORA_DAWN_FDM_IMPLEMENTATION
|
|
#include "aurora_dawn_fdm_abi.h"
|
|
#include "aurora_fdm.h"
|
|
#include "src/dawn/native/DynamicUploader.h"
|
|
#include "src/dawn/native/ResourceMemoryAllocation.h"
|
|
#include "src/dawn/native/vulkan/BufferVk.h"
|
|
#include "src/dawn/native/vulkan/CommandRecordingContextVk.h"
|
|
#include "src/dawn/native/vulkan/FencedDeleter.h"
|
|
#include "src/dawn/native/vulkan/QueueVk.h"
|
|
#include "src/dawn/native/vulkan/ResourceHeapVk.h"
|
|
#include "src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h"
|
|
#include "src/dawn/native/vulkan/VulkanError.h"
|
|
#include "absl/container/flat_hash_map.h"
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <mutex>
|
|
namespace dawn::native::vulkan {
|
|
namespace {
|
|
std::atomic<bool> auroraFdmRequested{false};
|
|
|
|
struct AuroraFdmMap {
|
|
VkImage image;
|
|
VkImageView view;
|
|
ResourceMemoryAllocation memory;
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
ExecutionSerial readySerial = kMaxExecutionSerial;
|
|
};
|
|
struct AuroraFdmBinding {
|
|
Ref<TextureViewBase> view;
|
|
uint64_t map = 0;
|
|
};
|
|
struct AuroraFdmState {
|
|
uint64_t nextMap = 1;
|
|
absl::flat_hash_map<uint64_t, AuroraFdmMap> maps;
|
|
absl::flat_hash_map<TextureViewBase*, AuroraFdmBinding> bindings;
|
|
};
|
|
// Per device. Also taken while a render pass is recorded, under the device lock Queue::Submit holds.
|
|
std::mutex auroraFdmMutex;
|
|
absl::flat_hash_map<Device*, std::unique_ptr<AuroraFdmState>> auroraFdmStates;
|
|
|
|
Device* AuroraFdmDevice(void* handle) {
|
|
return ToBackend(FromAPI(static_cast<WGPUDevice>(handle)));
|
|
}
|
|
|
|
bool AuroraFdmEnabled(Device* device) {
|
|
return device->GetDeviceInfo().HasExt(DeviceExt::FragmentDensityMap);
|
|
}
|
|
|
|
AuroraFdmState* AuroraFdmStateFor(Device* device, bool create) {
|
|
auto it = auroraFdmStates.find(device);
|
|
if (it != auroraFdmStates.end()) return it->second.get();
|
|
if (!create) return nullptr;
|
|
return auroraFdmStates.emplace(device, std::make_unique<AuroraFdmState>()).first->second.get();
|
|
}
|
|
|
|
bool AuroraFdmReady(Device* device, const AuroraFdmMap& map) {
|
|
return device->GetQueue()->GetCompletedCommandSerial() >= map.readySerial;
|
|
}
|
|
|
|
void AuroraFdmRelease(Device* device, AuroraFdmMap& map) {
|
|
if (map.view != VK_NULL_HANDLE) device->GetFencedDeleter()->DeleteWhenUnused(map.view);
|
|
if (map.image != VK_NULL_HANDLE) device->GetFencedDeleter()->DeleteWhenUnused(map.image);
|
|
device->GetResourceMemoryAllocator()->Deallocate(&map.memory);
|
|
map.view = VkImageView{};
|
|
map.image = VkImage{};
|
|
}
|
|
|
|
MaybeError AuroraFdmUpload(Device* device, uint32_t width, uint32_t height, const uint8_t* rg8, AuroraFdmMap& map) {
|
|
VkImageCreateInfo imageInfo{};
|
|
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
imageInfo.format = VK_FORMAT_R8G8_UNORM;
|
|
imageInfo.extent = {width, height, 1};
|
|
imageInfo.mipLevels = 1;
|
|
imageInfo.arrayLayers = 1;
|
|
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
imageInfo.usage = VK_IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
DAWN_TRY(CheckVkSuccess(device->fn.CreateImage(device->GetVkDevice(), &imageInfo, nullptr, &*map.image),
|
|
"CreateImage (fragment density map)"));
|
|
|
|
VkMemoryRequirements requirements;
|
|
device->fn.GetImageMemoryRequirements(device->GetVkDevice(), map.image, &requirements);
|
|
DAWN_TRY_ASSIGN(map.memory,
|
|
device->GetResourceMemoryAllocator()->Allocate(requirements, MemoryKind::DeviceLocal));
|
|
DAWN_TRY(CheckVkSuccess(device->fn.BindImageMemory(device->GetVkDevice(), map.image,
|
|
ToBackend(map.memory.GetResourceHeap())->GetMemory(),
|
|
map.memory.GetOffset()),
|
|
"BindImageMemory (fragment density map)"));
|
|
|
|
VkImageViewCreateInfo viewInfo{};
|
|
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
viewInfo.image = map.image;
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = VK_FORMAT_R8G8_UNORM;
|
|
viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
|
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
|
|
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
DAWN_TRY(CheckVkSuccess(device->fn.CreateImageView(device->GetVkDevice(), &viewInfo, nullptr, &*map.view),
|
|
"CreateImageView (fragment density map)"));
|
|
|
|
Queue* queue = ToBackend(device->GetQueue());
|
|
const uint64_t size = uint64_t(width) * height * 2;
|
|
DAWN_TRY(device->GetDynamicUploader()->WithUploadReservation(
|
|
size, device->GetOptimalBufferToTextureCopyOffsetAlignment(),
|
|
[&](UploadReservation reservation) -> MaybeError {
|
|
std::memcpy(reservation.mappedPointer.get(), rg8, size);
|
|
CommandRecordingContext* context = queue->GetPendingRecordingContext();
|
|
|
|
VkImageMemoryBarrier barrier{};
|
|
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
barrier.srcAccessMask = 0;
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.image = map.image;
|
|
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
device->fn.CmdPipelineBarrier(context->commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
|
|
|
VkBufferImageCopy region{};
|
|
region.bufferOffset = reservation.offsetInBuffer;
|
|
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
region.imageExtent = {width, height, 1};
|
|
device->fn.CmdCopyBufferToImage(context->commandBuffer, ToBackend(reservation.buffer.Get())->GetHandle(),
|
|
map.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
|
|
|
// A map without VK_IMAGE_VIEW_CREATE_FRAGMENT_DENSITY_MAP_DYNAMIC_BIT_EXT is read by the
|
|
// host when a render pass using it is recorded, so the upload is also made visible to
|
|
// host reads. The map is only used once this submission has completed.
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_FRAGMENT_DENSITY_MAP_READ_BIT_EXT | VK_ACCESS_HOST_READ_BIT;
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT;
|
|
device->fn.CmdPipelineBarrier(context->commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_PIPELINE_STAGE_FRAGMENT_DENSITY_PROCESS_BIT_EXT |
|
|
VK_PIPELINE_STAGE_HOST_BIT,
|
|
0, 0, nullptr, 0, nullptr, 1, &barrier);
|
|
return {};
|
|
}));
|
|
map.width = width;
|
|
map.height = height;
|
|
map.readySerial = queue->GetPendingCommandSerial();
|
|
DAWN_TRY(queue->SubmitPendingCommands());
|
|
return {};
|
|
}
|
|
} // namespace
|
|
|
|
bool AuroraFdmWanted(const VulkanDeviceInfo& info, bool dynamicRendering) {
|
|
return auroraFdmRequested.load() && dynamicRendering && info.HasExt(DeviceExt::FragmentDensityMap) &&
|
|
info.fragmentDensityMapFeatures.fragmentDensityMap == VK_TRUE &&
|
|
info.fragmentDensityMapFeatures.fragmentDensityMapNonSubsampledImages == VK_TRUE;
|
|
}
|
|
|
|
::VkImageView AuroraFdmViewFor(Device* device, TextureViewBase* view) {
|
|
if (view == nullptr || !AuroraFdmEnabled(device)) return VK_NULL_HANDLE;
|
|
std::lock_guard lock(auroraFdmMutex);
|
|
AuroraFdmState* state = AuroraFdmStateFor(device, false);
|
|
if (state == nullptr) return VK_NULL_HANDLE;
|
|
auto binding = state->bindings.find(view);
|
|
if (binding == state->bindings.end()) return VK_NULL_HANDLE;
|
|
auto map = state->maps.find(binding->second.map);
|
|
if (map == state->maps.end() || !AuroraFdmReady(device, map->second)) return VK_NULL_HANDLE;
|
|
return map->second.view;
|
|
}
|
|
|
|
void AuroraFdmDestroy(Device* device) {
|
|
std::unique_ptr<AuroraFdmState> state;
|
|
{
|
|
std::lock_guard lock(auroraFdmMutex);
|
|
auto it = auroraFdmStates.find(device);
|
|
if (it == auroraFdmStates.end()) return;
|
|
state = std::move(it->second);
|
|
auroraFdmStates.erase(it);
|
|
}
|
|
state->bindings.clear();
|
|
for (auto& [id, map] : state->maps) AuroraFdmRelease(device, map);
|
|
}
|
|
|
|
extern "C" DAWN_NATIVE_EXPORT uint32_t AuroraDawnFdmVersion(void) { return AURORA_DAWN_FDM_ABI; }
|
|
|
|
extern "C" DAWN_NATIVE_EXPORT void AuroraDawnFdmRequest(int enable) { auroraFdmRequested.store(enable != 0); }
|
|
|
|
extern "C" DAWN_NATIVE_EXPORT int AuroraDawnFdmQuery(void* handle, AuroraDawnFdmCaps* caps) {
|
|
Device* device = AuroraFdmDevice(handle);
|
|
auto guard = device->GetGuard();
|
|
const VulkanDeviceInfo& info = device->GetDeviceInfo();
|
|
*caps = {};
|
|
caps->enabled = AuroraFdmEnabled(device) ? 1 : 0;
|
|
if (info.HasExt(DeviceExt::FragmentDensityMap)) {
|
|
const auto& properties = info.fragmentDensityMapProperties;
|
|
caps->minTexelWidth = properties.minFragmentDensityTexelSize.width;
|
|
caps->minTexelHeight = properties.minFragmentDensityTexelSize.height;
|
|
caps->maxTexelWidth = properties.maxFragmentDensityTexelSize.width;
|
|
caps->maxTexelHeight = properties.maxFragmentDensityTexelSize.height;
|
|
}
|
|
return static_cast<int>(caps->enabled);
|
|
}
|
|
|
|
extern "C" DAWN_NATIVE_EXPORT uint64_t AuroraDawnFdmCreateMap(void* handle, uint32_t width, uint32_t height,
|
|
const uint8_t* rg8) {
|
|
Device* device = AuroraFdmDevice(handle);
|
|
auto guard = device->GetGuard();
|
|
if (!AuroraFdmEnabled(device) || width == 0 || height == 0 || rg8 == nullptr) return 0;
|
|
AuroraFdmMap map;
|
|
if (device->ConsumedError(AuroraFdmUpload(device, width, height, rg8, map))) {
|
|
AuroraFdmRelease(device, map);
|
|
return 0;
|
|
}
|
|
std::lock_guard lock(auroraFdmMutex);
|
|
AuroraFdmState* state = AuroraFdmStateFor(device, true);
|
|
const uint64_t id = state->nextMap++;
|
|
state->maps.emplace(id, std::move(map));
|
|
return id;
|
|
}
|
|
|
|
extern "C" DAWN_NATIVE_EXPORT int AuroraDawnFdmMapReady(void* handle, uint64_t id) {
|
|
Device* device = AuroraFdmDevice(handle);
|
|
auto guard = device->GetGuard();
|
|
std::lock_guard lock(auroraFdmMutex);
|
|
AuroraFdmState* state = AuroraFdmStateFor(device, false);
|
|
if (state == nullptr) return 0;
|
|
auto map = state->maps.find(id);
|
|
return map != state->maps.end() && AuroraFdmReady(device, map->second) ? 1 : 0;
|
|
}
|
|
|
|
extern "C" DAWN_NATIVE_EXPORT void AuroraDawnFdmReleaseMap(void* handle, uint64_t id) {
|
|
Device* device = AuroraFdmDevice(handle);
|
|
auto guard = device->GetGuard();
|
|
std::lock_guard lock(auroraFdmMutex);
|
|
AuroraFdmState* state = AuroraFdmStateFor(device, false);
|
|
if (state == nullptr) return;
|
|
auto map = state->maps.find(id);
|
|
if (map == state->maps.end()) return;
|
|
absl::erase_if(state->bindings, [id](const auto& binding) { return binding.second.map == id; });
|
|
AuroraFdmRelease(device, map->second);
|
|
state->maps.erase(map);
|
|
}
|
|
|
|
extern "C" DAWN_NATIVE_EXPORT int AuroraDawnFdmBind(void* handle, void* viewHandle, uint64_t id) {
|
|
Device* device = AuroraFdmDevice(handle);
|
|
auto guard = device->GetGuard();
|
|
TextureViewBase* view = FromAPI(static_cast<WGPUTextureView>(viewHandle));
|
|
if (view == nullptr) return 0;
|
|
std::lock_guard lock(auroraFdmMutex);
|
|
AuroraFdmState* state = AuroraFdmStateFor(device, id != 0);
|
|
if (state == nullptr) return 1;
|
|
if (id == 0) {
|
|
state->bindings.erase(view);
|
|
return 1;
|
|
}
|
|
auto map = state->maps.find(id);
|
|
if (map == state->maps.end()) return 0;
|
|
// VkRenderingInfo requires the map to reach the render area at the largest texel size.
|
|
const Extent3D size = view->GetSingleSubresourceVirtualSize();
|
|
const auto& properties = device->GetDeviceInfo().fragmentDensityMapProperties;
|
|
const uint64_t maxTexelWidth = std::max<uint32_t>(properties.maxFragmentDensityTexelSize.width, 1);
|
|
const uint64_t maxTexelHeight = std::max<uint32_t>(properties.maxFragmentDensityTexelSize.height, 1);
|
|
if (map->second.width * maxTexelWidth < size.width || map->second.height * maxTexelHeight < size.height) {
|
|
return 0;
|
|
}
|
|
state->bindings[view] = AuroraFdmBinding{Ref<TextureViewBase>(view), id};
|
|
return 1;
|
|
}
|
|
} // namespace dawn::native::vulkan
|