mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 01:00:14 +02:00
621 lines
22 KiB
C++
621 lines
22 KiB
C++
#include <aurora/vulkan_interop.h>
|
|
|
|
#include "../internal.hpp"
|
|
#include "../stereo.hpp"
|
|
#include "gpu.hpp"
|
|
|
|
#if defined(__ANDROID__) && defined(WEBGPU_DAWN)
|
|
|
|
#include <android/hardware_buffer.h>
|
|
#include <magic_enum.hpp>
|
|
#include <poll.h>
|
|
#include <unistd.h>
|
|
#include <vulkan/vulkan_core.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace aurora::vulkan_interop {
|
|
namespace {
|
|
|
|
Module Log("aurora::vulkan_interop");
|
|
|
|
int64_t to_vk_format(wgpu::TextureFormat format) noexcept {
|
|
switch (format) {
|
|
case wgpu::TextureFormat::RGBA8Unorm:
|
|
return VK_FORMAT_R8G8B8A8_UNORM;
|
|
case wgpu::TextureFormat::RGBA8UnormSrgb:
|
|
return VK_FORMAT_R8G8B8A8_SRGB;
|
|
case wgpu::TextureFormat::BGRA8Unorm:
|
|
return VK_FORMAT_B8G8R8A8_UNORM;
|
|
case wgpu::TextureFormat::BGRA8UnormSrgb:
|
|
return VK_FORMAT_B8G8R8A8_SRGB;
|
|
case wgpu::TextureFormat::RGBA16Float:
|
|
return VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
default:
|
|
return VK_FORMAT_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
// vkCmdCopyImage and WebGPU CopyTextureToTexture both require the two formats
|
|
// to be the same modulo sRGB encoding, so the bridge only accepts a target
|
|
// whose VkFormat sits in the same family as Aurora's eye output.
|
|
int copy_family(int64_t format) noexcept {
|
|
switch (format) {
|
|
case VK_FORMAT_R8G8B8A8_UNORM:
|
|
case VK_FORMAT_R8G8B8A8_SRGB:
|
|
return 1;
|
|
case VK_FORMAT_B8G8R8A8_UNORM:
|
|
case VK_FORMAT_B8G8R8A8_SRGB:
|
|
return 2;
|
|
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
|
return 3;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool same_copy_family(int64_t left, int64_t right) noexcept {
|
|
const int family = copy_family(left);
|
|
return family != 0 && family == copy_family(right);
|
|
}
|
|
|
|
void close_fd(int& fd) noexcept {
|
|
if (fd >= 0) {
|
|
::close(fd);
|
|
}
|
|
fd = -1;
|
|
}
|
|
|
|
// A sync file descriptor becomes readable once its fence signals, so a plain
|
|
// poll() is a CPU-side wait that needs no libsync.
|
|
void wait_sync_fd(int fd) noexcept {
|
|
if (fd < 0) {
|
|
return;
|
|
}
|
|
pollfd request{.fd = fd, .events = POLLIN, .revents = 0};
|
|
while (::poll(&request, 1, 1000) == 0) {
|
|
Log.warn("Waiting on a Dawn release fence took over a second");
|
|
}
|
|
}
|
|
|
|
bool device_supports_bridge() noexcept {
|
|
return webgpu::g_device && webgpu::g_backendType == wgpu::BackendType::Vulkan &&
|
|
webgpu::g_device.HasFeature(wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer) &&
|
|
webgpu::g_device.HasFeature(wgpu::FeatureName::SharedFenceSyncFD);
|
|
}
|
|
|
|
// One AHardwareBuffer imported into Dawn. The import is created on first use
|
|
// and kept until the bridge is disabled; the ring the OpenXR side recycles is
|
|
// tiny (two buffers per eye), so this never grows beyond a handful of entries.
|
|
struct Import {
|
|
AHardwareBuffer* buffer = nullptr;
|
|
wgpu::SharedTextureMemory memory;
|
|
wgpu::Texture texture;
|
|
wgpu::TextureFormat format = wgpu::TextureFormat::Undefined;
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
bool initialized = false;
|
|
bool accessBegun = false;
|
|
};
|
|
|
|
struct PendingTarget {
|
|
AHardwareBuffer* buffer = nullptr;
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
int64_t vkFormat = VK_FORMAT_UNDEFINED;
|
|
int acquireFenceFd = -1;
|
|
int32_t acquireImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
};
|
|
|
|
class StereoBridge final {
|
|
public:
|
|
StereoBridge(AuroraVulkanStereoSubmittedCallback callback, void* userdata) noexcept
|
|
: m_callback(callback), m_userdata(userdata) {}
|
|
|
|
~StereoBridge() { ReleaseImportsLocked(); }
|
|
|
|
bool Initialize() noexcept {
|
|
if (!device_supports_bridge()) {
|
|
Log.error("Dawn Vulkan device lacks SharedTextureMemoryAHardwareBuffer or SharedFenceSyncFD");
|
|
return false;
|
|
}
|
|
m_auroraFormat = webgpu::g_graphicsConfig.surfaceConfiguration.format;
|
|
return to_vk_format(m_auroraFormat) != VK_FORMAT_UNDEFINED;
|
|
}
|
|
|
|
bool PrepareForDestruction() noexcept {
|
|
std::lock_guard lock(m_mutex);
|
|
// Dawn's queue is drained by aurora_quiesce_frame_worker() before this is
|
|
// reached; all that can be outstanding here is a begun access whose
|
|
// EndAccess never ran because the frame was abandoned.
|
|
for (auto& [buffer, import] : m_imports) {
|
|
if (import.accessBegun) {
|
|
wgpu::SharedTextureMemoryEndAccessState end{};
|
|
import.memory.EndAccess(import.texture, &end);
|
|
import.accessBegun = false;
|
|
}
|
|
}
|
|
ReleaseImportsLocked();
|
|
return true;
|
|
}
|
|
|
|
bool SetTargets(uint64_t token, const AuroraVulkanStereoTarget* targets,
|
|
uint32_t targetCount) noexcept {
|
|
if (token == 0 || targets == nullptr || targetCount == 0 ||
|
|
targetCount > AURORA_VULKAN_STEREO_MAX_TARGETS) {
|
|
return false;
|
|
}
|
|
std::lock_guard lock(m_mutex);
|
|
if (m_framePending || m_encoded) {
|
|
return false;
|
|
}
|
|
const int64_t auroraFormat = to_vk_format(m_auroraFormat);
|
|
for (uint32_t eye = 0; eye < targetCount; ++eye) {
|
|
const auto& target = targets[eye];
|
|
if (target.buffer == nullptr || target.width == 0 || target.height == 0 ||
|
|
!same_copy_family(target.vkFormat, auroraFormat)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (uint32_t eye = 0; eye < targetCount; ++eye) {
|
|
m_targets[eye] = {
|
|
.buffer = targets[eye].buffer,
|
|
.width = targets[eye].width,
|
|
.height = targets[eye].height,
|
|
.vkFormat = targets[eye].vkFormat,
|
|
.acquireFenceFd = targets[eye].acquireFenceFd,
|
|
.acquireImageLayout = targets[eye].acquireImageLayout,
|
|
};
|
|
}
|
|
for (uint32_t eye = targetCount; eye < m_targets.size(); ++eye) {
|
|
m_targets[eye] = {};
|
|
}
|
|
m_frameToken = token;
|
|
m_targetCount = targetCount;
|
|
m_framePending = true;
|
|
return true;
|
|
}
|
|
|
|
bool Encode(wgpu::CommandEncoder& encoder, const stereo::SinkFrame& frame) noexcept {
|
|
std::lock_guard lock(m_mutex);
|
|
if (!m_framePending || m_encoded || frame.frameToken != m_frameToken) {
|
|
return false;
|
|
}
|
|
if (EncodeLocked(encoder, frame)) {
|
|
m_encoded = true;
|
|
return true;
|
|
}
|
|
PublishAndClearFrameLocked(frame.frameToken, false);
|
|
return false;
|
|
}
|
|
|
|
void Submitted(const stereo::SinkFrame& frame) noexcept {
|
|
std::lock_guard lock(m_mutex);
|
|
if (!m_framePending || !m_encoded || frame.frameToken != m_frameToken) {
|
|
return;
|
|
}
|
|
std::array<AuroraVulkanStereoRelease, AURORA_VULKAN_STEREO_MAX_TARGETS> releases{};
|
|
const bool success = EndAccessLocked(releases);
|
|
NotifyLocked(frame.frameToken, success, releases);
|
|
ClearFrameLocked();
|
|
}
|
|
|
|
void CancelPending() noexcept {
|
|
std::lock_guard lock(m_mutex);
|
|
if (!m_framePending) {
|
|
return;
|
|
}
|
|
const uint64_t token = m_frameToken;
|
|
std::array<AuroraVulkanStereoRelease, AURORA_VULKAN_STEREO_MAX_TARGETS> releases{};
|
|
if (m_encoded) {
|
|
EndAccessLocked(releases);
|
|
for (auto& release : releases) {
|
|
close_fd(release.releaseFenceFd);
|
|
}
|
|
}
|
|
PublishAndClearFrameLocked(token, false);
|
|
}
|
|
|
|
bool CancelBeforeEncode(uint64_t token) noexcept {
|
|
std::unique_lock lock(m_mutex, std::try_to_lock);
|
|
if (!lock.owns_lock()) {
|
|
return false;
|
|
}
|
|
if (token == 0 || !m_framePending || m_encoded || token != m_frameToken) {
|
|
return false;
|
|
}
|
|
ClearFrameLocked();
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
Import* EnsureImport(uint32_t eye, const stereo::EyeImage& source) noexcept {
|
|
const auto& target = m_targets[eye];
|
|
if (source.texture == nullptr || source.format != m_auroraFormat ||
|
|
source.size.width != target.width || source.size.height != target.height) {
|
|
Log.error("Stereo eye {} does not match its OpenXR Vulkan target ({}x{} vs {}x{})", eye,
|
|
source.size.width, source.size.height, target.width, target.height);
|
|
return nullptr;
|
|
}
|
|
if (auto found = m_imports.find(target.buffer); found != m_imports.end()) {
|
|
auto& import = found->second;
|
|
if (import.width == target.width && import.height == target.height &&
|
|
import.format == source.format) {
|
|
return &import;
|
|
}
|
|
Log.error("AHardwareBuffer for eye {} was re-used with a different geometry", eye);
|
|
return nullptr;
|
|
}
|
|
|
|
Import import;
|
|
import.buffer = target.buffer;
|
|
AHardwareBuffer_acquire(import.buffer);
|
|
|
|
wgpu::SharedTextureMemoryAHardwareBufferDescriptor ahb{};
|
|
ahb.handle = target.buffer;
|
|
const wgpu::SharedTextureMemoryDescriptor memoryDescriptor{
|
|
.nextInChain = &ahb,
|
|
.label = eye == 0 ? "OpenXR left eye AHardwareBuffer" : "OpenXR right eye AHardwareBuffer",
|
|
};
|
|
import.memory = webgpu::g_device.ImportSharedTextureMemory(&memoryDescriptor);
|
|
if (!import.memory) {
|
|
Log.error("Dawn rejected the AHardwareBuffer import for eye {}", eye);
|
|
AHardwareBuffer_release(import.buffer);
|
|
return nullptr;
|
|
}
|
|
wgpu::SharedTextureMemoryProperties properties{};
|
|
if (import.memory.GetProperties(&properties) != wgpu::Status::Success ||
|
|
properties.size.width != target.width || properties.size.height != target.height ||
|
|
(properties.usage & wgpu::TextureUsage::CopyDst) == wgpu::TextureUsage::None) {
|
|
Log.error("Dawn reported incompatible AHardwareBuffer properties for eye {}", eye);
|
|
AHardwareBuffer_release(import.buffer);
|
|
return nullptr;
|
|
}
|
|
if (properties.format != source.format) {
|
|
// The OpenXR side allocates R8G8B8A8_UNORM buffers because that is the only
|
|
// 8-bit RGBA AHardwareBuffer format; gpu.cpp steers Aurora to RGBA8Unorm
|
|
// under xrInterop so this mismatch only happens on a driver with no such
|
|
// surface format, where a copy could never be legal anyway.
|
|
Log.error("AHardwareBuffer format {} does not match Aurora's {} for eye {}",
|
|
magic_enum::enum_name(properties.format), magic_enum::enum_name(source.format), eye);
|
|
AHardwareBuffer_release(import.buffer);
|
|
return nullptr;
|
|
}
|
|
const wgpu::TextureDescriptor textureDescriptor{
|
|
.label = eye == 0 ? "OpenXR left eye shared texture" : "OpenXR right eye shared texture",
|
|
.usage = wgpu::TextureUsage::CopyDst,
|
|
.dimension = wgpu::TextureDimension::e2D,
|
|
.size = {target.width, target.height, 1},
|
|
.format = source.format,
|
|
.mipLevelCount = 1,
|
|
.sampleCount = 1,
|
|
};
|
|
import.texture = import.memory.CreateTexture(&textureDescriptor);
|
|
if (!import.texture) {
|
|
Log.error("Dawn could not wrap the AHardwareBuffer for eye {}", eye);
|
|
AHardwareBuffer_release(import.buffer);
|
|
return nullptr;
|
|
}
|
|
import.format = source.format;
|
|
import.width = target.width;
|
|
import.height = target.height;
|
|
const auto [inserted, ok] = m_imports.emplace(target.buffer, std::move(import));
|
|
return ok ? &inserted->second : nullptr;
|
|
}
|
|
|
|
bool EncodeLocked(wgpu::CommandEncoder& encoder, const stereo::SinkFrame& frame) noexcept {
|
|
std::array<Import*, AURORA_VULKAN_STEREO_MAX_TARGETS> imports{};
|
|
for (uint32_t eye = 0; eye < m_targetCount; ++eye) {
|
|
imports[eye] = EnsureImport(eye, frame.eyes[eye]);
|
|
if (imports[eye] == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
for (uint32_t eye = 0; eye < m_targetCount; ++eye) {
|
|
auto& target = m_targets[eye];
|
|
auto& import = *imports[eye];
|
|
if (import.accessBegun) {
|
|
Log.error("AHardwareBuffer for eye {} is still under a previous access", eye);
|
|
RollbackAccesses(imports, eye);
|
|
return false;
|
|
}
|
|
// The OpenXR side's release barrier leaves the image in acquireImageLayout;
|
|
// Dawn's acquire barrier must repeat exactly that old/new pair, then it
|
|
// transitions to its own copy-destination layout. A never-written buffer
|
|
// has undefined contents and layout, which Dawn treats as uninitialized.
|
|
const bool haveContents = import.initialized &&
|
|
target.acquireImageLayout != VK_IMAGE_LAYOUT_UNDEFINED;
|
|
wgpu::SharedTextureMemoryVkImageLayoutBeginState layout{};
|
|
layout.oldLayout = haveContents ? target.acquireImageLayout : VK_IMAGE_LAYOUT_UNDEFINED;
|
|
layout.newLayout = haveContents ? target.acquireImageLayout : VK_IMAGE_LAYOUT_GENERAL;
|
|
wgpu::SharedFence acquireFence;
|
|
if (target.acquireFenceFd >= 0) {
|
|
wgpu::SharedFenceSyncFDDescriptor syncFd{};
|
|
syncFd.handle = target.acquireFenceFd;
|
|
const wgpu::SharedFenceDescriptor fenceDescriptor{
|
|
.nextInChain = &syncFd,
|
|
.label = "OpenXR eye copy-out fence",
|
|
};
|
|
// Dawn duplicates the descriptor; the bridge still owns and closes its copy.
|
|
acquireFence = webgpu::g_device.ImportSharedFence(&fenceDescriptor);
|
|
close_fd(target.acquireFenceFd);
|
|
if (!acquireFence) {
|
|
Log.error("Dawn could not import the OpenXR copy-out fence for eye {}", eye);
|
|
RollbackAccesses(imports, eye);
|
|
return false;
|
|
}
|
|
}
|
|
const std::array fences{acquireFence};
|
|
// A sync file descriptor is binary; Dawn requires signaled value 1 for
|
|
// SyncFD fences ("signaled value (0) was not 1" otherwise).
|
|
const std::array<uint64_t, 1> values{1};
|
|
wgpu::SharedTextureMemoryBeginAccessDescriptor begin{};
|
|
begin.nextInChain = &layout;
|
|
begin.concurrentRead = false;
|
|
begin.initialized = haveContents;
|
|
if (acquireFence) {
|
|
begin.fenceCount = 1;
|
|
begin.fences = fences.data();
|
|
begin.signaledValueCount = 1;
|
|
begin.signaledValues = values.data();
|
|
}
|
|
if (import.memory.BeginAccess(import.texture, &begin) != wgpu::Status::Success) {
|
|
Log.error("Dawn BeginAccess failed for stereo eye {}", eye);
|
|
RollbackAccesses(imports, eye);
|
|
return false;
|
|
}
|
|
import.accessBegun = true;
|
|
}
|
|
for (uint32_t eye = 0; eye < m_targetCount; ++eye) {
|
|
const auto& import = *imports[eye];
|
|
const wgpu::TexelCopyTextureInfo source{
|
|
.texture = *frame.eyes[eye].texture,
|
|
.mipLevel = 0,
|
|
.origin = {},
|
|
.aspect = wgpu::TextureAspect::All,
|
|
};
|
|
const wgpu::TexelCopyTextureInfo destination{
|
|
.texture = import.texture,
|
|
.mipLevel = 0,
|
|
.origin = {},
|
|
.aspect = wgpu::TextureAspect::All,
|
|
};
|
|
const wgpu::Extent3D extent{import.width, import.height, 1};
|
|
encoder.CopyTextureToTexture(&source, &destination, &extent);
|
|
m_encodedImports[eye] = imports[eye];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void RollbackAccesses(const std::array<Import*, AURORA_VULKAN_STEREO_MAX_TARGETS>& imports,
|
|
uint32_t count) noexcept {
|
|
for (uint32_t eye = 0; eye < count; ++eye) {
|
|
if (imports[eye] != nullptr && imports[eye]->accessBegun) {
|
|
wgpu::SharedTextureMemoryEndAccessState end{};
|
|
imports[eye]->memory.EndAccess(imports[eye]->texture, &end);
|
|
imports[eye]->initialized = end.initialized;
|
|
imports[eye]->accessBegun = false;
|
|
}
|
|
}
|
|
for (auto& target : m_targets) {
|
|
close_fd(target.acquireFenceFd);
|
|
}
|
|
}
|
|
|
|
bool EndAccessLocked(
|
|
std::array<AuroraVulkanStereoRelease, AURORA_VULKAN_STEREO_MAX_TARGETS>& releases) noexcept {
|
|
bool success = true;
|
|
for (auto& release : releases) {
|
|
release = {.releaseFenceFd = -1, .releasedImageLayout = VK_IMAGE_LAYOUT_UNDEFINED};
|
|
}
|
|
for (uint32_t eye = 0; eye < m_targetCount; ++eye) {
|
|
Import* import = m_encodedImports[eye];
|
|
if (import == nullptr || !import->accessBegun) {
|
|
success = false;
|
|
continue;
|
|
}
|
|
wgpu::SharedTextureMemoryVkImageLayoutEndState layout{};
|
|
wgpu::SharedTextureMemoryEndAccessState end{};
|
|
end.nextInChain = &layout;
|
|
if (import->memory.EndAccess(import->texture, &end) != wgpu::Status::Success) {
|
|
Log.error("Dawn EndAccess failed for stereo eye {}", eye);
|
|
success = false;
|
|
} else {
|
|
import->initialized = end.initialized;
|
|
releases[eye].releasedImageLayout = layout.newLayout;
|
|
for (size_t i = 0; i < end.fenceCount; ++i) {
|
|
wgpu::SharedFenceSyncFDExportInfo syncFd{};
|
|
wgpu::SharedFenceExportInfo info{};
|
|
info.nextInChain = &syncFd;
|
|
end.fences[i].ExportInfo(&info);
|
|
if (info.type == wgpu::SharedFenceType::SyncFD && syncFd.handle >= 0) {
|
|
// The fence keeps its descriptor; hand the caller an independent one.
|
|
const int duplicate = ::dup(syncFd.handle);
|
|
if (duplicate >= 0) {
|
|
if (releases[eye].releaseFenceFd >= 0) {
|
|
// Dawn normally returns exactly one fence per access. Both must
|
|
// be honoured and one descriptor cannot express two fences, so
|
|
// the earlier one is retired on the CPU before handing over the
|
|
// latest.
|
|
Log.warn("Dawn returned several release fences for eye {}; merging on the CPU", eye);
|
|
wait_sync_fd(releases[eye].releaseFenceFd);
|
|
close_fd(releases[eye].releaseFenceFd);
|
|
}
|
|
releases[eye].releaseFenceFd = duplicate;
|
|
}
|
|
} else {
|
|
Log.error("Dawn returned a non-sync-fd fence for eye {}", eye);
|
|
success = false;
|
|
}
|
|
}
|
|
}
|
|
import->accessBegun = false;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
void ReleaseImportsLocked() noexcept {
|
|
for (auto& [buffer, import] : m_imports) {
|
|
import.texture = nullptr;
|
|
import.memory = nullptr;
|
|
if (import.buffer != nullptr) {
|
|
AHardwareBuffer_release(import.buffer);
|
|
}
|
|
}
|
|
m_imports.clear();
|
|
}
|
|
|
|
void ClearFrameLocked() noexcept {
|
|
for (auto& target : m_targets) {
|
|
close_fd(target.acquireFenceFd);
|
|
target = {};
|
|
}
|
|
m_encodedImports = {};
|
|
m_frameToken = 0;
|
|
m_targetCount = 0;
|
|
m_framePending = false;
|
|
m_encoded = false;
|
|
}
|
|
|
|
void PublishAndClearFrameLocked(uint64_t token, bool success) noexcept {
|
|
std::array<AuroraVulkanStereoRelease, AURORA_VULKAN_STEREO_MAX_TARGETS> releases{};
|
|
for (auto& release : releases) {
|
|
release = {.releaseFenceFd = -1, .releasedImageLayout = VK_IMAGE_LAYOUT_UNDEFINED};
|
|
}
|
|
NotifyLocked(token, success, releases);
|
|
ClearFrameLocked();
|
|
}
|
|
|
|
void NotifyLocked(uint64_t token, bool success,
|
|
const std::array<AuroraVulkanStereoRelease, AURORA_VULKAN_STEREO_MAX_TARGETS>&
|
|
releases) noexcept {
|
|
if (m_callback != nullptr) {
|
|
m_callback(token, success, releases.data(), m_targetCount, m_userdata);
|
|
} else {
|
|
for (auto release : releases) {
|
|
close_fd(release.releaseFenceFd);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::mutex m_mutex;
|
|
std::unordered_map<AHardwareBuffer*, Import> m_imports;
|
|
std::array<PendingTarget, AURORA_VULKAN_STEREO_MAX_TARGETS> m_targets{};
|
|
std::array<Import*, AURORA_VULKAN_STEREO_MAX_TARGETS> m_encodedImports{};
|
|
wgpu::TextureFormat m_auroraFormat = wgpu::TextureFormat::Undefined;
|
|
AuroraVulkanStereoSubmittedCallback m_callback = nullptr;
|
|
void* m_userdata = nullptr;
|
|
uint64_t m_frameToken = 0;
|
|
uint32_t m_targetCount = 0;
|
|
bool m_framePending = false;
|
|
bool m_encoded = false;
|
|
};
|
|
|
|
std::unique_ptr<StereoBridge> g_bridge;
|
|
|
|
bool sink_encode(wgpu::CommandEncoder& encoder, const stereo::SinkFrame& frame,
|
|
void* userdata) noexcept {
|
|
return static_cast<StereoBridge*>(userdata)->Encode(encoder, frame);
|
|
}
|
|
|
|
void sink_submitted(const stereo::SinkFrame& frame, void* userdata) noexcept {
|
|
static_cast<StereoBridge*>(userdata)->Submitted(frame);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace aurora::vulkan_interop
|
|
|
|
bool aurora_vulkan_get_native_handles(AuroraVulkanNativeHandles* handles) {
|
|
if (handles == nullptr) {
|
|
return false;
|
|
}
|
|
*handles = {};
|
|
using namespace aurora::vulkan_interop;
|
|
if (!aurora::webgpu::g_device || aurora::webgpu::g_backendType != wgpu::BackendType::Vulkan) {
|
|
return false;
|
|
}
|
|
const int64_t format = to_vk_format(aurora::webgpu::g_graphicsConfig.surfaceConfiguration.format);
|
|
if (format == VK_FORMAT_UNDEFINED) {
|
|
return false;
|
|
}
|
|
*handles = {
|
|
.colorVkFormat = format,
|
|
.sharedTextureMemoryAHardwareBuffer =
|
|
aurora::webgpu::g_device.HasFeature(wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer),
|
|
.sharedFenceSyncFd = aurora::webgpu::g_device.HasFeature(wgpu::FeatureName::SharedFenceSyncFD),
|
|
};
|
|
return true;
|
|
}
|
|
|
|
bool aurora_vulkan_enable_stereo_bridge(AuroraVulkanStereoSubmittedCallback submitted,
|
|
void* userdata) {
|
|
using namespace aurora::vulkan_interop;
|
|
if (g_bridge || submitted == nullptr) {
|
|
return false;
|
|
}
|
|
auto bridge = std::make_unique<StereoBridge>(submitted, userdata);
|
|
if (!bridge->Initialize()) {
|
|
return false;
|
|
}
|
|
aurora::stereo::set_sink(sink_encode, sink_submitted, bridge.get());
|
|
g_bridge = std::move(bridge);
|
|
return true;
|
|
}
|
|
|
|
bool aurora_vulkan_set_stereo_targets(uint64_t frameToken,
|
|
const AuroraVulkanStereoTarget* targets,
|
|
uint32_t targetCount) {
|
|
using namespace aurora::vulkan_interop;
|
|
return g_bridge && g_bridge->SetTargets(frameToken, targets, targetCount);
|
|
}
|
|
|
|
bool aurora_vulkan_cancel_stereo_targets(uint64_t frameToken) {
|
|
using namespace aurora::vulkan_interop;
|
|
return g_bridge && g_bridge->CancelBeforeEncode(frameToken);
|
|
}
|
|
|
|
bool aurora_vulkan_disable_stereo_bridge() {
|
|
using namespace aurora::vulkan_interop;
|
|
if (!g_bridge) {
|
|
return true;
|
|
}
|
|
aurora::stereo::set_sink(nullptr, nullptr, nullptr);
|
|
g_bridge->CancelPending();
|
|
if (!g_bridge->PrepareForDestruction()) {
|
|
(void)g_bridge.release();
|
|
return false;
|
|
}
|
|
g_bridge.reset();
|
|
return true;
|
|
}
|
|
|
|
#else
|
|
|
|
bool aurora_vulkan_get_native_handles(AuroraVulkanNativeHandles* handles) {
|
|
if (handles != nullptr) {
|
|
*handles = {};
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool aurora_vulkan_enable_stereo_bridge(AuroraVulkanStereoSubmittedCallback, void*) {
|
|
return false;
|
|
}
|
|
|
|
bool aurora_vulkan_set_stereo_targets(uint64_t, const AuroraVulkanStereoTarget*, uint32_t) {
|
|
return false;
|
|
}
|
|
|
|
bool aurora_vulkan_cancel_stereo_targets(uint64_t) { return false; }
|
|
|
|
bool aurora_vulkan_disable_stereo_bridge() { return true; }
|
|
|
|
#endif
|