Added foveated rendering for the Quest

- 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>
This commit is contained in:
iChris4andClaude Opus 5.5 committed 2026-09-24 19:03:17 +02:00
1 parent ceeeba0332
commit cc653272c8
30 files changed
+1527 -14

No files matched your search

+124 -7
View File
@@ -1,13 +1,47 @@
"""Apply the versioned Aurora native Vulkan ABI to the pinned Dawn source tree."""
"""Apply Aurora's native Dawn patches to the pinned Dawn source tree.
- The versioned native Vulkan ABI (aurora_vulkan_interop.inc): the OpenXR runtime creates Dawn's
Vulkan instance and device, used by the Windows Vulkan OpenXR binding.
- Fragment density maps (aurora_fdm.inc): VK_EXT_fragment_density_map on Aurora's immersive eye
render passes, used by the Quest build.
Every edit is anchored on Dawn source text, asserts that the anchor exists, and is skipped when it
is already applied, so the script can run again on a patched tree.
"""
from pathlib import Path
import shutil, sys
root = Path(sys.argv[1]).resolve()
here = Path(__file__).resolve().parent
for name in ("aurora_vulkan_hooks.h", "aurora_vulkan_interop.inc"):
shutil.copyfile(here / name, root / "src/dawn/native/vulkan" / name)
shutil.copyfile(here.parent.parent / "include/aurora/dawn_vulkan_abi.h",
root / "src/dawn/native/vulkan/aurora_dawn_vulkan_abi.h")
p = root / "src/dawn/native/vulkan/VulkanBackend.cpp"
vulkan = root / "src/dawn/native/vulkan"
include = here.parent.parent / "include/aurora"
def read(path):
with open(path, encoding="utf-8", newline="") as f:
return f.read()
def write(path, text):
with open(path, "w", encoding="utf-8", newline="") as f:
f.write(text)
def insert(path, anchor, text, after=True):
"""Insert `text` after (or before) the one occurrence of `anchor` unless it is already there."""
s = read(path)
if text in s:
return
assert s.count(anchor) == 1, f"{path.name}: expected one occurrence of {anchor!r}"
s = s.replace(anchor, anchor + text if after else text + anchor, 1)
write(path, s)
for name in ("aurora_vulkan_hooks.h", "aurora_vulkan_interop.inc", "aurora_fdm.h", "aurora_fdm.inc"):
shutil.copyfile(here / name, vulkan / name)
shutil.copyfile(include / "dawn_vulkan_abi.h", vulkan / "aurora_dawn_vulkan_abi.h")
shutil.copyfile(include / "dawn_fdm_abi.h", vulkan / "aurora_dawn_fdm_abi.h")
p = vulkan / "VulkanBackend.cpp"
s = p.read_text()
if '#include "aurora_vulkan_interop.inc"' not in s:
s += '\n#include "aurora_vulkan_interop.inc"\n'
@@ -23,7 +57,7 @@ new = '''((filename.size() > 2 && filename[1] == ':') || filename.starts_with("\
if old in s:
s = s.replace(old, new, 1)
p.write_text(s)
p = root / "src/dawn/native/vulkan/VulkanFunctions.cpp"
p = vulkan / "VulkanFunctions.cpp"
s = p.read_text()
if '#include "aurora_vulkan_hooks.h"' not in s:
marker = 'namespace dawn::native::vulkan {'
@@ -36,3 +70,86 @@ if '#include "aurora_vulkan_hooks.h"' not in s:
assert old in s, old
s = s.replace(old, old + '\n ' + new, 1)
p.write_text(s)
# Fragment density maps.
p = vulkan / "VulkanBackend.cpp"
s = read(p)
if '#include "aurora_fdm.inc"' not in s:
write(p, s + '#include "aurora_fdm.inc"\n')
insert(vulkan / "VulkanExtensions.h", " RasterizationOrderAttachmentAccess,\n",
" FragmentDensityMap,\n")
insert(vulkan / "VulkanExtensions.cpp",
' {DeviceExt::RasterizationOrderAttachmentAccess, "VK_EXT_rasterization_order_attachment_access"},\n',
' {DeviceExt::FragmentDensityMap, "VK_EXT_fragment_density_map"},\n')
insert(vulkan / "VulkanExtensions.cpp", " case DeviceExt::EnumCount:\n",
" // Aurora only attaches maps to dynamic rendering passes (aurora_fdm.inc).\n"
" case DeviceExt::FragmentDensityMap:\n"
" hasDependencies = HasDep(DeviceExt::DynamicRendering);\n"
" break;\n\n",
after=False)
insert(vulkan / "VulkanInfo.h", " VkPhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures;\n",
" VkPhysicalDeviceFragmentDensityMapFeaturesEXT fragmentDensityMapFeatures;\n")
insert(vulkan / "VulkanInfo.h", " VkPhysicalDeviceDrmPropertiesEXT drmProperties;\n",
" VkPhysicalDeviceFragmentDensityMapPropertiesEXT fragmentDensityMapProperties;\n")
insert(vulkan / "VulkanInfo.cpp",
" // Use vkGetPhysicalDevice{Features,Properties}2 if required to gather information about\n",
" if (info.extensions[DeviceExt::FragmentDensityMap]) {\n"
" featuresChain.Add(&info.fragmentDensityMapFeatures,\n"
" VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT);\n"
" propertiesChain.Add(&info.fragmentDensityMapProperties,\n"
" VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_PROPERTIES_EXT);\n"
" }\n\n",
after=False)
insert(vulkan / "DeviceVk.cpp", '#include "src/dawn/native/vulkan/DeviceVk.h"\n',
'#include "aurora_fdm.h"\n')
insert(vulkan / "DeviceVk.cpp", " usedKnobs.extensions = mDeviceInfo.extensions;\n",
" // Aurora: fragment density maps for its immersive eye passes, only on request\n"
" // (aurora_fdm.inc).\n"
" if (!AuroraFdmWanted(mDeviceInfo, IsToggleEnabled(Toggle::VulkanUseDynamicRendering) &&\n"
" !HasFeature(Feature::DawnLoadResolveTexture))) {\n"
" usedKnobs.extensions.set(DeviceExt::FragmentDensityMap, false);\n"
" }\n")
insert(vulkan / "DeviceVk.cpp",
" usedKnobs.extendedDynamicStateFeatures = mDeviceInfo.extendedDynamicStateFeatures;\n"
" featuresChain.Add(&usedKnobs.extendedDynamicStateFeatures);\n"
" }\n",
"\n"
" if (usedKnobs.HasExt(DeviceExt::FragmentDensityMap)) {\n"
" usedKnobs.fragmentDensityMapFeatures = mDeviceInfo.fragmentDensityMapFeatures;\n"
" // Aurora's maps are immutable, read when a render pass is recorded.\n"
" usedKnobs.fragmentDensityMapFeatures.fragmentDensityMapDynamic = VK_FALSE;\n"
" featuresChain.Add(&usedKnobs.fragmentDensityMapFeatures);\n"
" }\n")
insert(vulkan / "DeviceVk.cpp", " mStaticSamplerCache.clear();\n",
"\n AuroraFdmDestroy(this);\n")
insert(vulkan / "RenderPipelineVk.cpp", " createInfo.renderPass = nullRenderPass;\n",
"\n"
" // Aurora: any pipeline may draw in an eye pass with a fragment density map\n"
" // (aurora_fdm.inc).\n"
" if (device->GetDeviceInfo().HasExt(DeviceExt::FragmentDensityMap)) {\n"
" createInfo.flags |= VK_PIPELINE_CREATE_RENDERING_FRAGMENT_DENSITY_MAP_ATTACHMENT_BIT_EXT;\n"
" }\n")
insert(vulkan / "CommandBufferVk.cpp", '#include "src/dawn/native/vulkan/CommandBufferVk.h"\n',
'#include "aurora_fdm.h"\n')
insert(vulkan / "CommandBufferVk.cpp", " // TODO(crbug.com/463893794): Handle ExpandResolveTexture.\n",
" // Aurora: the fragment density map bound to the first color attachment's view\n"
" // (aurora_fdm.inc).\n"
" VkRenderingFragmentDensityMapAttachmentInfoEXT fragmentDensityMap;\n"
" if (attachmentMask[ColorAttachmentIndex(uint8_t(0))]) {\n"
" const ::VkImageView densityMap = AuroraFdmViewFor(\n"
" device, renderPass->colorAttachments[ColorAttachmentIndex(uint8_t(0))].view.Get());\n"
" if (densityMap != VK_NULL_HANDLE) {\n"
" fragmentDensityMap.sType =\n"
" VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_DENSITY_MAP_ATTACHMENT_INFO_EXT;\n"
" fragmentDensityMap.pNext = renderInfo.pNext;\n"
" fragmentDensityMap.imageView = densityMap;\n"
" fragmentDensityMap.imageLayout = VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT;\n"
" renderInfo.pNext = &fragmentDensityMap;\n"
" }\n"
" }\n\n",
after=False)
+21
View File
@@ -0,0 +1,21 @@
// Included only by the pinned Dawn source build (apply.py): fragment density maps for Aurora's
// immersive eye render passes. Implemented in aurora_fdm.inc.
#pragma once
#include "src/dawn/common/vulkan_platform.h"
namespace dawn::native {
class TextureViewBase;
}
namespace dawn::native::vulkan {
class Device;
struct VulkanDeviceInfo;
// Device::CreateDevice keeps VK_EXT_fragment_density_map only when Aurora asked for it and the
// device can attach a map to a dynamic rendering pass over ordinary (non-subsampled) images.
bool AuroraFdmWanted(const VulkanDeviceInfo& info, bool dynamicRendering);
// The density map for a render pass whose first color attachment is `view`, or a null handle.
::VkImageView AuroraFdmViewFor(Device* device, TextureViewBase* view);
// Device::DestroyImpl: releases every map while the fenced deleter still runs.
void AuroraFdmDestroy(Device* device);
} // namespace dawn::native::vulkan
+279
View File
@@ -0,0 +1,279 @@
// 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, &region);
// 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