import_plane closed the shared NT handle on every pyrowave_image_create
failure, believing pyrowave only consumes it on success. The vendored
truth was messier: Granite's allocator closed by-reference handles
UNCONDITIONALLY after the first vkAllocateMemory — success AND failure —
while failures before the allocator (validation, vkCreateImage, no
memory type) left the handle open, and both classes surface as the same
error code. So the call site could not know whether to close, and an
allocate-stage failure double-closed a possibly-recycled handle value
(audit WP5.3). The filed fix (DuplicateHandle + close the original
unconditionally) traded the double close for a guaranteed leak on every
pre-allocate failure and left the ambiguity in place.
Patch 0006 fixes the callee instead: the allocator never closes a
caller's handle, and pyrowave_image_create consumes it exactly at its
success return — which is what pyrowave.h ("take ownership and close the
HANDLE on import") documents, and what Granite's semaphore import
already did, so import_fence was correct as written and the two paths
now share one contract. The close-on-failure in import_plane is thereby
correct on EVERY path, including a vkBindImageMemory failure after a
successful allocate. Bonus fix recorded in the patch: the allocator's
block-recycling retry loop used to re-run vkAllocateMemory with
import_info still pointing at the just-closed handle.
Both-platform gates green; pyrowave_win_smoke (.173, 34/34) re-validates
the live import path against the rebuilt vendored C++.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1526 lines
50 KiB
C++
1526 lines
50 KiB
C++
// Copyright (c) 2026 Hans-Kristian Arntzen
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "context.hpp"
|
|
#include "device.hpp"
|
|
#include "image.hpp"
|
|
#include "buffer.hpp"
|
|
#include "pyrowave.h"
|
|
#include "pyrowave_decoder.hpp"
|
|
#include "pyrowave_encoder.hpp"
|
|
#include "logging.hpp"
|
|
|
|
using namespace Granite;
|
|
using namespace Vulkan;
|
|
using namespace PyroWave;
|
|
|
|
struct NullLogger : Util::LoggingInterface
|
|
{
|
|
bool log(const char *, const char *, va_list) override
|
|
{
|
|
#ifdef VULKAN_DEBUG
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
};
|
|
|
|
static NullLogger null_logger;
|
|
|
|
extern "C" {
|
|
void pyrowave_get_api_version(uint32_t *major, uint32_t *minor, uint32_t *patch)
|
|
{
|
|
*major = PYROWAVE_API_VERSION_MAJOR;
|
|
*minor = PYROWAVE_API_VERSION_MINOR;
|
|
*patch = PYROWAVE_API_VERSION_PATCH;
|
|
}
|
|
|
|
struct pyrowave_device_opaque
|
|
{
|
|
Context context;
|
|
Device device;
|
|
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
|
CommandBuffer::Type queue_type = CommandBuffer::Type::Generic;
|
|
};
|
|
|
|
void pyrowave_device_set_command_buffer(pyrowave_device device, VkCommandBuffer cmd)
|
|
{
|
|
device->cmd = cmd;
|
|
}
|
|
|
|
pyrowave_result pyrowave_create_device_by_compat(
|
|
// If non-zero, needs to match VkPhysicalDeviceProperties::vendorID/deviceID.
|
|
// Risks picking the wrong device if there are multiple ICDs for the same GPU.
|
|
uint32_t vid, uint32_t pid,
|
|
const pyrowave_uuid *device_uuid, // If non-NULL, needs to match VkPhysicalDeviceIDProperties::deviceUUID
|
|
const pyrowave_uuid *driver_uuid, // If non-NULL, needs to match VkPhysicalDeviceIDProperties::driverUUID
|
|
const pyrowave_luid *device_luid, // If non-NULL, needs to match VkPhysicalDeviceIDProperties::deviceLUID
|
|
pyrowave_device *device)
|
|
{
|
|
// TODO: Find a better way to do this.
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
|
|
if (!Context::init_loader(nullptr))
|
|
return PYROWAVE_ERROR_NO_VULKAN;
|
|
|
|
auto *dev = new pyrowave_device_opaque();
|
|
dev->context.set_num_thread_indices(1);
|
|
dev->context.set_system_handles({});
|
|
|
|
VkApplicationInfo app_info = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
|
|
app_info.apiVersion = VK_API_VERSION_1_2;
|
|
app_info.pApplicationName = "pyrowave-c";
|
|
app_info.pEngineName = "Granite";
|
|
dev->context.set_application_info(&app_info);
|
|
|
|
// Just enable video extensions so that we can use video image usage, but don't bother creating queues for it, etc.
|
|
if (!dev->context.init_instance(nullptr, 0, CONTEXT_CREATION_ENABLE_VIDEO_FEATURE_ONLY_BIT))
|
|
{
|
|
delete dev;
|
|
return PYROWAVE_ERROR_NO_VULKAN;
|
|
}
|
|
|
|
uint32_t count;
|
|
if (vkEnumeratePhysicalDevices(dev->context.get_instance(), &count, nullptr) != VK_SUCCESS)
|
|
{
|
|
delete dev;
|
|
return PYROWAVE_ERROR_NO_VULKAN;
|
|
}
|
|
|
|
std::vector<VkPhysicalDevice> gpus(count);
|
|
|
|
if (vkEnumeratePhysicalDevices(dev->context.get_instance(), &count, gpus.data()) < 0)
|
|
{
|
|
delete dev;
|
|
return PYROWAVE_ERROR_NO_VULKAN;
|
|
}
|
|
|
|
VkPhysicalDevice selected_gpu = VK_NULL_HANDLE;
|
|
|
|
for (auto &gpu : gpus)
|
|
{
|
|
VkPhysicalDeviceProperties props = {};
|
|
vkGetPhysicalDeviceProperties(gpu, &props);
|
|
// Is this even possible these days?
|
|
if (props.apiVersion < VK_API_VERSION_1_2)
|
|
continue;
|
|
|
|
VkPhysicalDeviceIDProperties ids = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES };
|
|
VkPhysicalDeviceProperties2 props2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &ids };
|
|
vkGetPhysicalDeviceProperties2(gpu, &props2);
|
|
|
|
if (vid && props2.properties.vendorID != vid)
|
|
continue;
|
|
if (pid && props2.properties.deviceID != pid)
|
|
continue;
|
|
|
|
if (device_uuid && memcmp(device_uuid, ids.deviceUUID, VK_UUID_SIZE) != 0)
|
|
continue;
|
|
if (driver_uuid && memcmp(driver_uuid, ids.driverUUID, VK_UUID_SIZE) != 0)
|
|
continue;
|
|
if (device_luid && !ids.deviceLUIDValid)
|
|
continue;
|
|
if (device_luid && memcmp(device_luid, ids.deviceLUID, VK_LUID_SIZE) != 0)
|
|
continue;
|
|
|
|
if (dev->context.init_device(gpu, VK_NULL_HANDLE, nullptr, 0, CONTEXT_CREATION_ENABLE_VIDEO_FEATURE_ONLY_BIT))
|
|
{
|
|
selected_gpu = gpu;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!selected_gpu)
|
|
{
|
|
delete dev;
|
|
return PYROWAVE_ERROR_NO_VULKAN;
|
|
}
|
|
|
|
dev->device.set_context(dev->context);
|
|
*device = dev;
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
pyrowave_result pyrowave_create_default_device(pyrowave_device *device)
|
|
{
|
|
return pyrowave_create_device_by_compat(0, 0, nullptr, nullptr, nullptr, device);
|
|
}
|
|
|
|
static std::mutex global_device_lock;
|
|
|
|
pyrowave_result pyrowave_create_device(const pyrowave_device_create_info *info, pyrowave_device *out_device)
|
|
{
|
|
// TODO: Find a better way to do this.
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
|
|
// Safety against concurrent device creations since we're setting global function pointer state here.
|
|
std::lock_guard<std::mutex> holder{global_device_lock};
|
|
|
|
if (!Context::init_loader(info->GetInstanceProcAddr))
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
struct MyInstanceFactory : InstanceFactory
|
|
{
|
|
const pyrowave_device_create_info *info = nullptr;
|
|
|
|
VkInstance create_instance(const VkInstanceCreateInfo *) override
|
|
{
|
|
return info->instance;
|
|
}
|
|
|
|
// Lifetime of any data in create info must remain as long as Context is alive.
|
|
const VkInstanceCreateInfo *get_existing_create_info() override
|
|
{
|
|
return info->instance_create_info;
|
|
}
|
|
|
|
bool factory_owns_created_instance() override
|
|
{
|
|
return true;
|
|
}
|
|
} instance;
|
|
|
|
struct MyDeviceFactory : DeviceFactory
|
|
{
|
|
const pyrowave_device_create_info *info = nullptr;
|
|
|
|
VkDevice create_device(VkPhysicalDevice, const VkDeviceCreateInfo *) override
|
|
{
|
|
return info->device;
|
|
}
|
|
|
|
const VkDeviceCreateInfo *get_existing_create_info() override
|
|
{
|
|
return info->device_create_info;
|
|
}
|
|
|
|
bool factory_owns_created_device() override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
VkQueue get_queue(uint32_t family_index, uint32_t index) override
|
|
{
|
|
for (uint32_t i = 0; i < info->queue_info_count; i++)
|
|
if (info->queue_info[i].familyIndex == family_index && info->queue_info[i].index == index)
|
|
return info->queue_info[i].queue;
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
} device;
|
|
|
|
instance.info = info;
|
|
device.info = info;
|
|
|
|
auto dev = new pyrowave_device_opaque;
|
|
dev->context.set_instance_factory(&instance);
|
|
dev->context.set_device_factory(&device);
|
|
|
|
if (!dev->context.init_instance(nullptr, 0))
|
|
return PYROWAVE_ERROR_NO_VULKAN;
|
|
|
|
if (!dev->context.init_device(info->physical_device, VK_NULL_HANDLE, nullptr, 0))
|
|
return PYROWAVE_ERROR_NO_VULKAN;
|
|
|
|
dev->device.set_context(dev->context);
|
|
|
|
dev->device.set_queue_lock(
|
|
[cb = info->queue_lock_callback, userdata = info->userdata]() {
|
|
if (cb)
|
|
cb(userdata);
|
|
},
|
|
[cb = info->queue_unlock_callback, userdata = info->userdata]() {
|
|
if (cb)
|
|
cb(userdata);
|
|
});
|
|
|
|
*out_device = dev;
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
void pyrowave_device_report_performance_stats(pyrowave_device device, pyrowave_message_cb cb, void *userdata, bool reset)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
|
|
device->device.timestamp_log([=](const std::string &tag, const TimestampIntervalReport &report)
|
|
{
|
|
char msg[256];
|
|
snprintf(msg, sizeof(msg), "%s: %.3f ms per frame\n", tag.c_str(), report.time_per_frame_context * 1e3);
|
|
cb(userdata, msg);
|
|
});
|
|
|
|
if (reset)
|
|
device->device.timestamp_log_reset();
|
|
}
|
|
|
|
void pyrowave_device_get_vk_device_handles(
|
|
pyrowave_device device,
|
|
VkInstance *vk_instance, VkPhysicalDevice *vk_physical_device,
|
|
VkDevice *vk_device)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
|
|
if (vk_instance)
|
|
*vk_instance = device->device.get_instance();
|
|
if (vk_physical_device)
|
|
*vk_physical_device = device->device.get_physical_device();
|
|
if (vk_device)
|
|
*vk_device = device->device.get_device();
|
|
}
|
|
|
|
static bool pyrowave_device_confirm_external_semaphore_support(pyrowave_device device)
|
|
{
|
|
VkSemaphoreTypeCreateInfo type_info = { VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO };
|
|
VkPhysicalDeviceExternalSemaphoreInfo sem_info =
|
|
{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, &type_info };
|
|
VkExternalSemaphoreProperties sem_props = { VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES };
|
|
|
|
#ifdef _WIN32
|
|
sem_info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT;
|
|
#else
|
|
sem_info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
|
|
#endif
|
|
|
|
type_info.semaphoreType = VK_SEMAPHORE_TYPE_BINARY;
|
|
vkGetPhysicalDeviceExternalSemaphoreProperties(device->device.get_physical_device(), &sem_info, &sem_props);
|
|
if (!(sem_props.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT))
|
|
return false;
|
|
|
|
type_info.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
|
|
vkGetPhysicalDeviceExternalSemaphoreProperties(device->device.get_physical_device(), &sem_info, &sem_props);
|
|
if (!(sem_props.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT))
|
|
return false;
|
|
|
|
#ifdef _WIN32
|
|
// Despite being a timeline, D3D12_FENCE was added before TIMELINE was added, and AMD drivers have
|
|
// at least gotten confused when trying to use TIMELINE type here in the past.
|
|
sem_info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT;
|
|
sem_info.pNext = nullptr;
|
|
vkGetPhysicalDeviceExternalSemaphoreProperties(device->device.get_physical_device(), &sem_info, &sem_props);
|
|
if (!(sem_props.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT))
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool pyrowave_device_confirm_external_memory_support(pyrowave_device device)
|
|
{
|
|
VkExternalImageFormatProperties external_props = { VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES };
|
|
VkImageFormatProperties2 props2 = { VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, &external_props };
|
|
VkPhysicalDeviceExternalImageFormatInfo external_format_info =
|
|
{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO };
|
|
|
|
// TODO: Android?
|
|
#ifndef _WIN32
|
|
if (!device->device.get_device_features().supports_drm_modifiers)
|
|
return false;
|
|
#endif
|
|
|
|
static const VkExternalMemoryHandleTypeFlagBits required_external_types[] = {
|
|
#ifdef _WIN32
|
|
VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT,
|
|
VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT,
|
|
VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT,
|
|
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT,
|
|
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT,
|
|
#else
|
|
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT,
|
|
VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
|
|
#endif
|
|
};
|
|
|
|
VkPhysicalDeviceImageDrmFormatModifierInfoEXT modifier_info =
|
|
{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT };
|
|
|
|
for (auto type : required_external_types)
|
|
{
|
|
external_format_info.handleType = type;
|
|
external_format_info.pNext = nullptr;
|
|
|
|
if (type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT)
|
|
{
|
|
VkDrmFormatModifierPropertiesEXT mod;
|
|
VkDrmFormatModifierPropertiesListEXT modifier_list =
|
|
{ VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT };
|
|
modifier_list.drmFormatModifierCount = 1;
|
|
modifier_list.pDrmFormatModifierProperties = &mod;
|
|
VkFormatProperties3 props3 = { VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3, &modifier_list };
|
|
device->device.get_format_properties(VK_FORMAT_R8_UNORM, &props3);
|
|
|
|
if (modifier_list.drmFormatModifierCount != 1)
|
|
return false;
|
|
|
|
modifier_info.drmFormatModifier = mod.drmFormatModifier;
|
|
modifier_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
external_format_info.pNext = &modifier_info;
|
|
}
|
|
|
|
if (!device->device.get_image_format_properties(
|
|
VK_FORMAT_R8_UNORM, VK_IMAGE_TYPE_2D, type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT ?
|
|
VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT : VK_IMAGE_TILING_OPTIMAL,
|
|
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
|
0, &external_format_info, &props2))
|
|
return false;
|
|
|
|
if (!(external_props.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pyrowave_device_confirm_interop_support(pyrowave_device device)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
if (!device->device.get_device_features().supports_external)
|
|
return false;
|
|
if (!pyrowave_device_confirm_external_semaphore_support(device))
|
|
return false;
|
|
if (!pyrowave_device_confirm_external_memory_support(device))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_device_set_queue_type(pyrowave_device device, VkQueueFlagBits queue_flags)
|
|
{
|
|
if (!device)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
if (queue_flags != VK_QUEUE_GRAPHICS_BIT && queue_flags != VK_QUEUE_COMPUTE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
device->queue_type = queue_flags == VK_QUEUE_GRAPHICS_BIT ? CommandBuffer::Type::Generic : CommandBuffer::Type::AsyncCompute;
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
void pyrowave_device_destroy(pyrowave_device device)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
|
|
delete device;
|
|
}
|
|
|
|
struct pyrowave_sync_object_opaque
|
|
{
|
|
Device *device = nullptr;
|
|
Semaphore semaphore;
|
|
};
|
|
|
|
pyrowave_result
|
|
pyrowave_sync_object_create(const pyrowave_sync_object_create_info *info, pyrowave_sync_object *out_sync)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
|
|
if (!info->device)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if ((info->handle_type & (
|
|
VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT |
|
|
VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT |
|
|
VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT |
|
|
VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT |
|
|
VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT)) == 0)
|
|
{
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
auto &device = info->device->device;
|
|
|
|
if (!device.get_device_features().supports_external)
|
|
return PYROWAVE_ERROR_NOT_IMPLEMENTED;
|
|
|
|
auto sem = device.request_semaphore_external(info->semaphore_type, info->handle_type);
|
|
if (!sem)
|
|
return PYROWAVE_ERROR_UNSUPPORTED_EXTERNAL_HANDLE;
|
|
|
|
ExternalHandle ext = {};
|
|
ext.handle = (decltype(ext.handle))info->external_handle;
|
|
ext.semaphore_handle_type = info->handle_type;
|
|
if (ext && !sem->import_from_handle(ext))
|
|
return PYROWAVE_ERROR_FAILED_EXTERNAL_HANDLE;
|
|
|
|
if (!ext && !(info->import_flags & VK_SEMAPHORE_IMPORT_TEMPORARY_BIT))
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
auto *sync = new pyrowave_sync_object_opaque();
|
|
sync->device = &device;
|
|
sync->semaphore = std::move(sem);
|
|
|
|
*out_sync = sync;
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
VkSemaphore
|
|
pyrowave_sync_object_get_semaphore(pyrowave_sync_object sync)
|
|
{
|
|
if (!sync)
|
|
return VK_NULL_HANDLE;
|
|
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
return sync->semaphore->get_semaphore();
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_sync_object_export_handle(pyrowave_sync_object sync, pyrowave_os_handle *handle)
|
|
{
|
|
if (!sync)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
if (auto native_handle = sync->semaphore->export_to_handle())
|
|
{
|
|
*handle = (pyrowave_os_handle)native_handle.handle;
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
return PYROWAVE_ERROR_FAILED_EXTERNAL_HANDLE;
|
|
}
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_sync_object_cpu_wait(pyrowave_sync_object sync, uint64_t value, uint64_t timeout)
|
|
{
|
|
if (!sync || sync->semaphore->get_semaphore_type() != VK_SEMAPHORE_TYPE_TIMELINE)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
return sync->semaphore->wait_timeline_timeout(value, timeout) ? PYROWAVE_SUCCESS : PYROWAVE_TIMEOUT;
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_sync_object_cpu_signal(pyrowave_sync_object sync, uint64_t value)
|
|
{
|
|
if (!sync || sync->semaphore->get_semaphore_type() != VK_SEMAPHORE_TYPE_TIMELINE)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
auto &table = sync->device->get_device_table();
|
|
|
|
VkSemaphoreSignalInfo signal_info = { VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO };
|
|
signal_info.semaphore = sync->semaphore->get_semaphore();
|
|
signal_info.value = value;
|
|
VkResult vr;
|
|
|
|
if (table.vkSignalSemaphore)
|
|
vr = table.vkSignalSemaphore(sync->device->get_device(), &signal_info);
|
|
else if (table.vkSignalSemaphoreKHR)
|
|
vr = table.vkSignalSemaphoreKHR(sync->device->get_device(), &signal_info);
|
|
else
|
|
return PYROWAVE_ERROR_GENERIC;
|
|
|
|
return vr == VK_SUCCESS ? PYROWAVE_SUCCESS : PYROWAVE_ERROR_GENERIC;
|
|
}
|
|
|
|
void pyrowave_sync_object_destroy(pyrowave_sync_object sync)
|
|
{
|
|
auto *device = sync->device;
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
delete sync;
|
|
device->next_frame_context();
|
|
}
|
|
|
|
struct pyrowave_image_opaque
|
|
{
|
|
Device *device = nullptr;
|
|
ImageHandle img;
|
|
};
|
|
|
|
pyrowave_result pyrowave_image_create(const pyrowave_image_create_info *info, pyrowave_image *out_image)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
if (!info->device || !info->image_create_info)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
auto &device = info->device->device;
|
|
|
|
if (info->image_create_info->sharingMode != VK_SHARING_MODE_EXCLUSIVE)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (info->handle_type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT &&
|
|
info->image_create_info->tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (info->handle_type != VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT &&
|
|
info->image_create_info->tiling != VK_IMAGE_TILING_OPTIMAL)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (info->image_create_info->imageType != VK_IMAGE_TYPE_2D)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
ImageCreateInfo image_create_info = {};
|
|
image_create_info.domain = ImageDomain::Physical;
|
|
image_create_info.misc = IMAGE_MISC_EXTERNAL_MEMORY_BIT | IMAGE_MISC_NO_DEFAULT_VIEWS_BIT;
|
|
image_create_info.external.handle = (decltype(image_create_info.external.handle))info->external_handle;
|
|
image_create_info.external.memory_handle_type = info->handle_type;
|
|
image_create_info.pnext = const_cast<void *>(info->image_create_info->pNext);
|
|
image_create_info.layout = ImageLayout::General;
|
|
image_create_info.initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
image_create_info.type = info->image_create_info->imageType;
|
|
image_create_info.format = info->image_create_info->format;
|
|
image_create_info.flags = info->image_create_info->flags;
|
|
image_create_info.width = info->image_create_info->extent.width;
|
|
image_create_info.height = info->image_create_info->extent.height;
|
|
image_create_info.depth = info->image_create_info->extent.depth;
|
|
image_create_info.layers = info->image_create_info->arrayLayers;
|
|
image_create_info.levels = info->image_create_info->mipLevels;
|
|
image_create_info.samples = info->image_create_info->samples;
|
|
image_create_info.usage = info->image_create_info->usage;
|
|
|
|
if (device.get_device_features().driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY &&
|
|
(info->handle_type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT ||
|
|
info->handle_type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT))
|
|
{
|
|
VkFormatProperties3 format_properties = { VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3 };
|
|
device.get_format_properties(image_create_info.format, &format_properties);
|
|
|
|
if (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_2_VIDEO_ENCODE_INPUT_BIT_KHR)
|
|
{
|
|
// NVIDIA workaround. For planar formats, the D3D side assumes video compatible layouts.
|
|
image_create_info.usage |= VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR;
|
|
|
|
// If we're on an older driver, just pass it through as-is.
|
|
// Normally we have to pass down a codec profile, but this is mostly noise.
|
|
if (device.get_device_features().video_maintenance1_features.videoMaintenance1)
|
|
image_create_info.flags |= VK_IMAGE_CREATE_VIDEO_PROFILE_INDEPENDENT_BIT_KHR;
|
|
}
|
|
}
|
|
|
|
auto img = device.create_image(image_create_info);
|
|
if (!img)
|
|
return PYROWAVE_ERROR_FAILED_EXTERNAL_HANDLE;
|
|
|
|
// PUNKTFUNK (patch 0006): consume the imported by-reference NT handle exactly here, at the
|
|
// API's success boundary — the allocator no longer closes it (see memory_allocator.cpp).
|
|
// This pins pyrowave.h's documented contract ("take ownership and close the HANDLE on
|
|
// import") to mean ON SUCCESS, matching pyrowave_sync_object_create's semantics: on ANY
|
|
// failure return the caller still owns the handle and can close it unconditionally.
|
|
// By-reference NT-handle imports never transfer ownership at the Vulkan level (the
|
|
// implementation keeps its own reference), so a post-create close here is always legal.
|
|
#ifdef _WIN32
|
|
if (info->external_handle &&
|
|
ExternalHandle::memory_handle_type_imports_by_reference(info->handle_type))
|
|
{
|
|
::CloseHandle(reinterpret_cast<HANDLE>(info->external_handle));
|
|
}
|
|
#endif
|
|
|
|
auto *image = new pyrowave_image_opaque();
|
|
image->device = &device;
|
|
image->img = std::move(img);
|
|
|
|
*out_image = image;
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
VkImage pyrowave_image_get_handle(pyrowave_image image)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
return image->img->get_image();
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_image_get_image_view(pyrowave_image image, VkImageAspectFlagBits aspect,
|
|
VkImageUsageFlagBits usage, pyrowave_image_view *view)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
|
|
if ((aspect & (VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT)) == 0)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
if (usage != VK_IMAGE_USAGE_SAMPLED_BIT && usage != VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
auto &img = *image->img;
|
|
|
|
*view = {};
|
|
view->image = img.get_image();
|
|
view->image_format = img.get_format();
|
|
view->width = img.get_width();
|
|
view->height = img.get_height();
|
|
view->layout = VK_IMAGE_LAYOUT_GENERAL;
|
|
|
|
// Handle the usual suspects.
|
|
switch (img.get_format())
|
|
{
|
|
// Normal explicit planar formats.
|
|
case VK_FORMAT_R8_UNORM:
|
|
case VK_FORMAT_R16_UNORM:
|
|
view->aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
view->swizzle = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
view->view_format = img.get_format();
|
|
break;
|
|
|
|
case VK_FORMAT_R8G8_UNORM:
|
|
case VK_FORMAT_R16G16_UNORM:
|
|
if (usage == VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
if (aspect == VK_IMAGE_ASPECT_PLANE_0_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
view->aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
view->swizzle = aspect == VK_IMAGE_ASPECT_PLANE_2_BIT ? VK_COMPONENT_SWIZZLE_G : VK_COMPONENT_SWIZZLE_R;
|
|
view->view_format = img.get_format();
|
|
break;
|
|
|
|
// Special 4:4:4 HDR10 format
|
|
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
|
|
if (usage == VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
view->aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
view->view_format = img.get_format();
|
|
|
|
switch (aspect)
|
|
{
|
|
case VK_IMAGE_ASPECT_PLANE_0_BIT: view->swizzle = VK_COMPONENT_SWIZZLE_R; break;
|
|
case VK_IMAGE_ASPECT_PLANE_1_BIT: view->swizzle = VK_COMPONENT_SWIZZLE_G; break;
|
|
case VK_IMAGE_ASPECT_PLANE_2_BIT: view->swizzle = VK_COMPONENT_SWIZZLE_B; break;
|
|
default: return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
break;
|
|
|
|
// 3-plane YCbCr
|
|
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
|
|
case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
|
|
view->view_format = VK_FORMAT_R8_UNORM;
|
|
view->aspect = aspect;
|
|
break;
|
|
|
|
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
|
|
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
|
|
view->view_format = VK_FORMAT_R10X6_UNORM_PACK16;
|
|
view->aspect = aspect;
|
|
break;
|
|
|
|
case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
|
|
case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
|
|
view->view_format = VK_FORMAT_R16_UNORM;
|
|
view->aspect = aspect;
|
|
break;
|
|
|
|
// 2-plane YCbCr
|
|
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
|
|
case VK_FORMAT_G8_B8R8_2PLANE_444_UNORM:
|
|
switch (aspect)
|
|
{
|
|
case VK_IMAGE_ASPECT_PLANE_0_BIT:
|
|
view->view_format = VK_FORMAT_R8_UNORM;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_0_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_ASPECT_PLANE_1_BIT:
|
|
if (usage == VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
view->swizzle = VK_COMPONENT_SWIZZLE_R;
|
|
view->view_format = VK_FORMAT_R8G8_UNORM;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_1_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_ASPECT_PLANE_2_BIT:
|
|
if (usage == VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
view->swizzle = VK_COMPONENT_SWIZZLE_G;
|
|
view->view_format = VK_FORMAT_R8G8_UNORM;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_1_BIT;
|
|
break;
|
|
|
|
default:
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
break;
|
|
|
|
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
|
|
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16:
|
|
switch (aspect)
|
|
{
|
|
case VK_IMAGE_ASPECT_PLANE_0_BIT:
|
|
view->view_format = VK_FORMAT_R10X6_UNORM_PACK16;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_0_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_ASPECT_PLANE_1_BIT:
|
|
if (usage == VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
view->swizzle = VK_COMPONENT_SWIZZLE_R;
|
|
view->view_format = VK_FORMAT_R10X6G10X6_UNORM_2PACK16;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_1_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_ASPECT_PLANE_2_BIT:
|
|
if (usage == VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
view->swizzle = VK_COMPONENT_SWIZZLE_G;
|
|
view->view_format = VK_FORMAT_R10X6G10X6_UNORM_2PACK16;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_1_BIT;
|
|
break;
|
|
|
|
default:
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
break;
|
|
|
|
case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
|
|
case VK_FORMAT_G16_B16R16_2PLANE_444_UNORM:
|
|
switch (aspect)
|
|
{
|
|
case VK_IMAGE_ASPECT_PLANE_0_BIT:
|
|
view->view_format = VK_FORMAT_R16_UNORM;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_0_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_ASPECT_PLANE_1_BIT:
|
|
if (usage == VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
view->swizzle = VK_COMPONENT_SWIZZLE_R;
|
|
view->view_format = VK_FORMAT_R16G16_UNORM;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_1_BIT;
|
|
break;
|
|
|
|
case VK_IMAGE_ASPECT_PLANE_2_BIT:
|
|
if (usage == VK_IMAGE_USAGE_STORAGE_BIT)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
view->swizzle = VK_COMPONENT_SWIZZLE_G;
|
|
view->view_format = VK_FORMAT_R16G16_UNORM;
|
|
view->aspect = VK_IMAGE_ASPECT_PLANE_1_BIT;
|
|
break;
|
|
|
|
default:
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return PYROWAVE_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
void pyrowave_image_destroy(pyrowave_image image)
|
|
{
|
|
auto *device = image->device;
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
delete image;
|
|
|
|
// Pump frame contexts through to make sure memory gets freed eventually.
|
|
device->next_frame_context();
|
|
}
|
|
|
|
struct pyrowave_encoder_opaque
|
|
{
|
|
Device *device = nullptr;
|
|
pyrowave_device pyro_device = nullptr;
|
|
Encoder encoder;
|
|
Fence queued_fence;
|
|
BufferHandle queued_meta;
|
|
BufferHandle queued_bitstream;
|
|
// PUNKTFUNK: the GPU-side twins of queued_meta/queued_bitstream, pooled on the encoder so
|
|
// the encode path reuses them across frames instead of allocating four buffers per frame.
|
|
BufferHandle queued_meta_gpu;
|
|
BufferHandle queued_bitstream_gpu;
|
|
ChromaSubsampling chroma = {};
|
|
int width = 0;
|
|
int height = 0;
|
|
};
|
|
|
|
pyrowave_result
|
|
pyrowave_encoder_create(const pyrowave_encoder_create_info *info, pyrowave_encoder *encoder)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
|
|
if (!info->device)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (info->width <= 0 || info->height <= 0)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (info->chroma == PYROWAVE_CHROMA_SUBSAMPLING_420 && (info->width % 2 || info->height % 2))
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
auto *enc = new pyrowave_encoder_opaque();
|
|
enc->pyro_device = info->device;
|
|
enc->device = &info->device->device;
|
|
enc->chroma = ChromaSubsampling(info->chroma);
|
|
enc->width = info->width;
|
|
enc->height = info->height;
|
|
|
|
if (!enc->encoder.init(&info->device->device, info->width, info->height, enc->chroma))
|
|
{
|
|
delete enc;
|
|
return PYROWAVE_ERROR_GENERIC;
|
|
}
|
|
|
|
*encoder = enc;
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
struct WrappedViewBuffers : ViewBuffers
|
|
{
|
|
ImageHandle wrapped_images[3];
|
|
ImageViewHandle image_views[3];
|
|
bool wrap(Device *device, const pyrowave_gpu_buffers *buffers, VkImageUsageFlags usage);
|
|
};
|
|
|
|
bool WrappedViewBuffers::wrap(Device *device, const pyrowave_gpu_buffers *buffers, VkImageUsageFlags usage)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
ImageCreateInfo image_info = {};
|
|
image_info.usage = usage;
|
|
image_info.type = VK_IMAGE_TYPE_2D;
|
|
image_info.domain = ImageDomain::Physical;
|
|
image_info.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
image_info.width = buffers->planes[i].width;
|
|
image_info.height = buffers->planes[i].height;
|
|
image_info.format = buffers->planes[i].image_format;
|
|
|
|
// The exact numbers aren't important.
|
|
image_info.layers = buffers->planes[i].layer + 1;
|
|
image_info.levels = buffers->planes[i].mip_level + 1;
|
|
|
|
image_info.layout =
|
|
buffers->planes[i].layout == VK_IMAGE_LAYOUT_GENERAL ? ImageLayout::General : ImageLayout::Optimal;
|
|
wrapped_images[i] = device->wrap_image(image_info, buffers->planes[i].image);
|
|
if (!wrapped_images[i])
|
|
return false;
|
|
|
|
ImageViewCreateInfo view_info = {};
|
|
view_info.image = wrapped_images[i].get();
|
|
view_info.format = buffers->planes[i].view_format;
|
|
view_info.view_type = VK_IMAGE_VIEW_TYPE_2D;
|
|
view_info.layers = 1;
|
|
view_info.levels = 1;
|
|
view_info.base_level = buffers->planes[i].mip_level;
|
|
view_info.base_layer = buffers->planes[i].layer;
|
|
view_info.swizzle.r = buffers->planes[i].swizzle;
|
|
view_info.swizzle.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
view_info.swizzle.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
view_info.swizzle.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
view_info.aspect = buffers->planes[i].aspect;
|
|
image_views[i] = device->create_image_view(view_info);
|
|
if (!image_views[i])
|
|
return false;
|
|
|
|
planes[i] = image_views[i].get();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void pyrowave_device_wait_semaphore(Device *device, CommandBuffer::Type queue_type, const pyrowave_gpu_sync_operation *acquire, VkPipelineStageFlags2 stages)
|
|
{
|
|
if (acquire && acquire->sync.semaphore != VK_NULL_HANDLE)
|
|
{
|
|
auto sem = device->request_semaphore(
|
|
acquire->sync.value != 0 ? VK_SEMAPHORE_TYPE_TIMELINE : VK_SEMAPHORE_TYPE_BINARY, acquire->sync.semaphore);
|
|
sem->signal_external();
|
|
|
|
if (acquire->sync.value)
|
|
{
|
|
sem = device->request_timeline_semaphore_as_binary(*sem, acquire->sync.value);
|
|
sem->signal_external();
|
|
}
|
|
|
|
device->add_wait_semaphore(queue_type, std::move(sem), stages, false);
|
|
}
|
|
}
|
|
|
|
static void pyrowave_device_signal_semaphore(Device *device, CommandBuffer::Type queue_type, const pyrowave_gpu_sync_operation *release)
|
|
{
|
|
if (release && release->sync.semaphore != VK_NULL_HANDLE)
|
|
{
|
|
auto signal = device->request_semaphore(
|
|
release->sync.value != 0 ? VK_SEMAPHORE_TYPE_TIMELINE : VK_SEMAPHORE_TYPE_BINARY, release->sync.semaphore);
|
|
|
|
if (release->sync.value)
|
|
signal = device->request_timeline_semaphore_as_binary(*signal, release->sync.value);
|
|
|
|
if (signal)
|
|
device->submit_empty(queue_type, nullptr, signal.get());
|
|
}
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_encoder_encode_gpu_synchronous(pyrowave_encoder encoder,
|
|
const pyrowave_gpu_sync_operation *acquire,
|
|
const pyrowave_gpu_sync_operation *release,
|
|
const pyrowave_gpu_buffers *buffers,
|
|
const pyrowave_rate_control *rate_control)
|
|
{
|
|
if (encoder->pyro_device->cmd && (acquire || release))
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
auto *device = encoder->device;
|
|
|
|
device->next_frame_context();
|
|
|
|
auto target_bitstream_size = rate_control->maximum_bitstream_size & ~VkDeviceSize(3u);
|
|
|
|
// Check for bogus sizes.
|
|
if (target_bitstream_size > UINT32_MAX || target_bitstream_size == 0)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
const VkDeviceSize meta_size = encoder->encoder.get_meta_required_size();
|
|
const VkDeviceSize bitstream_size = target_bitstream_size + meta_size;
|
|
|
|
// PUNKTFUNK: pool the four scratch buffers on the encoder and only (re)create one when a
|
|
// larger size is needed. Upstream allocated all four (meta + bitstream, each Device +
|
|
// CachedHost) on every call; at streaming rates (240 fps, MB-scale bitstreams) that
|
|
// allocator churn dominated the per-frame CPU cost. The sizes are effectively constant per
|
|
// session (fixed resolution + a pinned bitrate budget), so after the first frame these are
|
|
// pure reuse. The synchronous encode model (packetize()/compute_num_packets() wait the
|
|
// fence before the next encode reuses a buffer) guarantees no in-flight GPU access to a
|
|
// buffer we hand back, and holding the handles keeps next_frame_context() from recycling
|
|
// them.
|
|
BufferCreateInfo bufinfo = {};
|
|
bufinfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
|
|
|
auto ensure_buffer = [&](BufferHandle &handle, VkDeviceSize size, BufferDomain domain) -> bool {
|
|
if (!handle || handle->get_create_info().size < size)
|
|
{
|
|
bufinfo.size = size;
|
|
bufinfo.domain = domain;
|
|
handle = device->create_buffer(bufinfo);
|
|
}
|
|
return bool(handle);
|
|
};
|
|
|
|
auto &queued_meta_gpu = encoder->queued_meta_gpu;
|
|
auto &queued_bitstream_gpu = encoder->queued_bitstream_gpu;
|
|
|
|
if (!ensure_buffer(encoder->queued_meta, meta_size, BufferDomain::CachedHost))
|
|
return PYROWAVE_ERROR_OUT_OF_HOST_MEMORY;
|
|
if (!ensure_buffer(queued_meta_gpu, meta_size, BufferDomain::Device))
|
|
return PYROWAVE_ERROR_OUT_OF_DEVICE_MEMORY;
|
|
if (!ensure_buffer(encoder->queued_bitstream, bitstream_size, BufferDomain::CachedHost))
|
|
return PYROWAVE_ERROR_OUT_OF_HOST_MEMORY;
|
|
if (!ensure_buffer(queued_bitstream_gpu, bitstream_size, BufferDomain::Device))
|
|
return PYROWAVE_ERROR_OUT_OF_DEVICE_MEMORY;
|
|
|
|
Encoder::BitstreamBuffers bitstream_buffers = {};
|
|
|
|
WrappedViewBuffers views = {};
|
|
if (!views.wrap(device, buffers, VK_IMAGE_USAGE_SAMPLED_BIT))
|
|
return PYROWAVE_ERROR_OUT_OF_HOST_MEMORY;
|
|
|
|
bitstream_buffers.meta.buffer = queued_meta_gpu.get();
|
|
bitstream_buffers.meta.size = queued_meta_gpu->get_create_info().size;
|
|
bitstream_buffers.bitstream.buffer = queued_bitstream_gpu.get();
|
|
bitstream_buffers.bitstream.size = queued_bitstream_gpu->get_create_info().size;
|
|
bitstream_buffers.target_size = target_bitstream_size;
|
|
|
|
auto cmd =
|
|
encoder->pyro_device->cmd
|
|
? device->request_borrowed_command_buffer(encoder->pyro_device->cmd)
|
|
: device->request_command_buffer(encoder->pyro_device->queue_type);
|
|
|
|
if (acquire)
|
|
{
|
|
for (size_t i = 0; i < acquire->num_images; i++)
|
|
{
|
|
cmd->acquire_image_barrier(*acquire->images[i].image->img,
|
|
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
|
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
VK_ACCESS_2_SHADER_SAMPLED_READ_BIT,
|
|
acquire->images[i].queue_family_index);
|
|
}
|
|
}
|
|
|
|
auto ret = encoder->encoder.encode(*cmd, views, bitstream_buffers);
|
|
if (!ret)
|
|
{
|
|
device->submit_discard(cmd);
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
if (release)
|
|
{
|
|
for (size_t i = 0; i < release->num_images; i++)
|
|
{
|
|
cmd->release_image_barrier(*release->images[i].image->img,
|
|
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
|
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
|
|
release->images[i].queue_family_index);
|
|
}
|
|
}
|
|
|
|
// NVIDIA really doesn't like it if we write bitstream to cached host.
|
|
// Performance issue since these memory types are mapped coherent on the GPU.
|
|
// A staging copy is just better. Could avoid it on iGPU, but iGPU isn't really supposed to be
|
|
// used as the encoder when streaming.
|
|
cmd->copy_buffer(*encoder->queued_meta, *queued_meta_gpu);
|
|
cmd->copy_buffer(*encoder->queued_bitstream, *queued_bitstream_gpu);
|
|
|
|
cmd->barrier(VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_HOST_READ_BIT);
|
|
|
|
pyrowave_device_wait_semaphore(device, encoder->pyro_device->queue_type, acquire, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
|
encoder->queued_fence.reset();
|
|
|
|
if (encoder->pyro_device->cmd)
|
|
{
|
|
device->submit_discard(cmd);
|
|
|
|
// Technicality, image views we created have not been submitted yet to Vulkan.
|
|
// Need to signal the GPU queues before we can move the context along.
|
|
device->submit_external(encoder->pyro_device->queue_type);
|
|
}
|
|
else
|
|
device->submit(cmd, &encoder->queued_fence);
|
|
|
|
pyrowave_device_signal_semaphore(device, encoder->pyro_device->queue_type, release);
|
|
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_encoder_encode_cpu_synchronous(pyrowave_encoder encoder, const pyrowave_cpu_buffer *buffers,
|
|
const pyrowave_rate_control *rate_control)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
int num_planes = buffers->format == PYROWAVE_CPU_BUFFER_FORMAT_NV12 ? 2 : 3;
|
|
auto *device = encoder->device;
|
|
ImageHandle images[3];
|
|
|
|
// Validate some assumptions.
|
|
if (buffers->width != encoder->width || buffers->height != encoder->height)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (encoder->chroma == ChromaSubsampling::Chroma420 && buffers->format == PYROWAVE_CPU_BUFFER_FORMAT_YUV444P)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
if (encoder->chroma == ChromaSubsampling::Chroma444 && buffers->format != PYROWAVE_CPU_BUFFER_FORMAT_YUV444P)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
for (int plane = 0; plane < num_planes; plane++)
|
|
{
|
|
int plane_width = encoder->width;
|
|
int plane_height = encoder->height;
|
|
|
|
if (plane != 0 && encoder->chroma == ChromaSubsampling::Chroma420)
|
|
{
|
|
plane_width /= 2;
|
|
plane_height /= 2;
|
|
}
|
|
|
|
const size_t plane_bpp = num_planes == 2 && plane == 1 ? 2 : 1;
|
|
|
|
if (buffers->row_stride_in_bytes[plane] < plane_width * plane_bpp)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
if (buffers->row_stride_in_bytes[plane] * plane_height > buffers->plane_size_in_bytes[plane])
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
for (int plane = 0; plane < num_planes; plane++)
|
|
{
|
|
unsigned plane_bpp = num_planes == 2 && plane == 1 ? 2 : 1;
|
|
|
|
const ImageInitialData initial = {
|
|
buffers->data[plane],
|
|
uint32_t(buffers->row_stride_in_bytes[plane] / plane_bpp)
|
|
};
|
|
|
|
auto info = ImageCreateInfo::immutable_2d_image(
|
|
buffers->width, buffers->height,
|
|
plane_bpp == 2 ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8_UNORM);
|
|
|
|
if (plane != 0 && encoder->chroma == ChromaSubsampling::Chroma420)
|
|
{
|
|
info.width /= 2;
|
|
info.height /= 2;
|
|
}
|
|
|
|
images[plane] = device->create_image(info, &initial);
|
|
if (!images[plane])
|
|
return PYROWAVE_ERROR_OUT_OF_DEVICE_MEMORY;
|
|
}
|
|
|
|
pyrowave_gpu_buffers gpu_buffers = {};
|
|
|
|
for (int plane = 0; plane < 3; plane++)
|
|
{
|
|
auto &p = gpu_buffers.planes[plane];
|
|
p.width = images[plane] ? images[plane]->get_width() : images[1]->get_width();
|
|
p.height = images[plane] ? images[plane]->get_height() : images[1]->get_height();
|
|
|
|
p.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
p.swizzle = num_planes == 2 && plane == 2 ? VK_COMPONENT_SWIZZLE_G : VK_COMPONENT_SWIZZLE_R;
|
|
p.image_format = images[plane] ? images[plane]->get_format() : VK_FORMAT_R8G8_UNORM;
|
|
p.view_format = p.image_format;
|
|
p.layout = images[plane]
|
|
? images[plane]->get_layout(VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL)
|
|
: images[1]->get_layout(VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL);
|
|
p.image = images[plane] ? images[plane]->get_image() : images[1]->get_image();
|
|
}
|
|
|
|
auto ret = pyrowave_encoder_encode_gpu_synchronous(encoder, nullptr, nullptr, &gpu_buffers, rate_control);
|
|
return ret;
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_encoder_compute_num_packets(pyrowave_encoder encoder, size_t packet_boundary, size_t *num_packets)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
if (encoder->queued_fence)
|
|
encoder->queued_fence->wait();
|
|
|
|
if (!encoder->queued_meta)
|
|
return PYROWAVE_ERROR_GENERIC;
|
|
|
|
auto *mapped_meta = encoder->device->map_host_buffer(*encoder->queued_meta, MEMORY_ACCESS_READ_BIT);
|
|
*num_packets = encoder->encoder.compute_num_packets(mapped_meta, packet_boundary);
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_encoder_packetize(pyrowave_encoder encoder, pyrowave_packet *packets, size_t packet_boundary,
|
|
size_t *out_packets, void *bitstream, size_t size)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
if (encoder->queued_fence)
|
|
encoder->queued_fence->wait();
|
|
|
|
if (!encoder->queued_meta || !encoder->queued_bitstream)
|
|
return PYROWAVE_ERROR_GENERIC;
|
|
|
|
auto *mapped_meta = encoder->device->map_host_buffer(*encoder->queued_meta, MEMORY_ACCESS_READ_BIT);
|
|
auto *mapped_bitstream = encoder->device->map_host_buffer(*encoder->queued_bitstream, MEMORY_ACCESS_READ_BIT);
|
|
|
|
*out_packets = encoder->encoder.packetize(
|
|
reinterpret_cast<Encoder::Packet *>(packets), packet_boundary, bitstream,
|
|
size, mapped_meta, mapped_bitstream);
|
|
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
void pyrowave_encoder_destroy(pyrowave_encoder encoder)
|
|
{
|
|
auto *device = encoder->device;
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
delete encoder;
|
|
device->next_frame_context();
|
|
}
|
|
|
|
struct pyrowave_decoder_opaque
|
|
{
|
|
Device *device = nullptr;
|
|
pyrowave_device pyro_device = nullptr;
|
|
Decoder decoder;
|
|
ImageHandle planes[3];
|
|
bool fragment_path = false;
|
|
ChromaSubsampling chroma = {};
|
|
int width = 0;
|
|
int height = 0;
|
|
};
|
|
|
|
bool pyrowave_decoder_device_prefers_fragment_path(pyrowave_device device)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
return Decoder::device_prefers_fragment_path(device->device);
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_decoder_create(const pyrowave_decoder_create_info *info, pyrowave_decoder *decoder)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
if (!info->device)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (info->width <= 0 || info->height <= 0)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (info->chroma == PYROWAVE_CHROMA_SUBSAMPLING_420 && (info->width % 2 || info->height % 2))
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
auto *dec = new pyrowave_decoder_opaque();
|
|
dec->pyro_device = info->device;
|
|
dec->device = &info->device->device;
|
|
dec->chroma = ChromaSubsampling(info->chroma);
|
|
dec->fragment_path = info->fragment_path;
|
|
dec->width = info->width;
|
|
dec->height = info->height;
|
|
|
|
if (!dec->decoder.init(dec->device, info->width, info->height, dec->chroma, info->fragment_path))
|
|
{
|
|
delete dec;
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*decoder = dec;
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
void pyrowave_decoder_clear(pyrowave_decoder decoder)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
decoder->decoder.clear();
|
|
}
|
|
|
|
// A frame is potentially split into multiple packets.
|
|
pyrowave_result
|
|
pyrowave_decoder_push_packet(pyrowave_decoder decoder, const void *data, size_t size)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
bool ret = decoder->decoder.push_packet(data, size);
|
|
return ret ? PYROWAVE_SUCCESS : PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// For error correction purposes, it may be okay to decode a frame which dropped some packets.
|
|
bool pyrowave_decoder_decode_is_ready(pyrowave_decoder decoder, bool allow_partial_frame)
|
|
{
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
return decoder->decoder.decode_is_ready(allow_partial_frame);
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_decoder_decode_gpu_buffer(pyrowave_decoder decoder,
|
|
const pyrowave_gpu_sync_operation *acquire,
|
|
const pyrowave_gpu_sync_operation *release,
|
|
const pyrowave_gpu_buffers *buffers)
|
|
{
|
|
if (decoder->pyro_device->cmd && (acquire || release))
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
auto *device = decoder->device;
|
|
device->next_frame_context();
|
|
|
|
WrappedViewBuffers views = {};
|
|
if (!views.wrap(device, buffers, decoder->fragment_path ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : VK_IMAGE_USAGE_STORAGE_BIT))
|
|
return PYROWAVE_ERROR_OUT_OF_HOST_MEMORY;
|
|
|
|
// Just use normal graphics queue here since the result will likely be consumed there.
|
|
auto cmd = decoder->pyro_device->cmd
|
|
? device->request_borrowed_command_buffer(decoder->pyro_device->cmd)
|
|
: device->request_command_buffer(decoder->pyro_device->queue_type);
|
|
|
|
VkPipelineStageFlags2 stages = decoder->fragment_path
|
|
? VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
|
|
: VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
|
|
|
VkAccessFlags2 access = decoder->fragment_path
|
|
? VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT
|
|
: VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
|
|
|
|
if (acquire)
|
|
{
|
|
for (size_t i = 0; i < acquire->num_images; i++)
|
|
{
|
|
if (acquire->images[i].queue_family_index != VK_QUEUE_FAMILY_IGNORED)
|
|
{
|
|
cmd->acquire_image_barrier(*acquire->images[i].image->img,
|
|
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
|
|
stages, access, acquire->images[i].queue_family_index);
|
|
}
|
|
else
|
|
{
|
|
cmd->image_barrier(*acquire->images[i].image->img,
|
|
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL,
|
|
stages, 0, stages, access);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto ret = decoder->decoder.decode(*cmd, views);
|
|
if (!ret)
|
|
{
|
|
device->submit_discard(cmd);
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
if (release)
|
|
{
|
|
for (size_t i = 0; i < release->num_images; i++)
|
|
{
|
|
cmd->release_image_barrier(*release->images[i].image->img,
|
|
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
|
|
stages, access, release->images[i].queue_family_index);
|
|
}
|
|
}
|
|
|
|
pyrowave_device_wait_semaphore(device, decoder->pyro_device->queue_type, acquire, stages);
|
|
|
|
// This just queues up a command buffer, flush only happens when sync objects are signaled.
|
|
if (decoder->pyro_device->cmd)
|
|
{
|
|
device->submit_discard(cmd);
|
|
|
|
// Technicality, image views we created have not been submitted yet to Vulkan.
|
|
// Need to signal the GPU queues before we can move the context along.
|
|
device->submit_external(decoder->pyro_device->queue_type);
|
|
}
|
|
else
|
|
device->submit(cmd);
|
|
|
|
pyrowave_device_signal_semaphore(device, decoder->pyro_device->queue_type, release);
|
|
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
pyrowave_result
|
|
pyrowave_decoder_decode_cpu_buffer_synchronous(pyrowave_decoder decoder, const pyrowave_cpu_buffer *buffers)
|
|
{
|
|
if (decoder->pyro_device->cmd)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
auto *device = decoder->device;
|
|
|
|
if (buffers->width != decoder->width || buffers->height != decoder->height)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (buffers->format == PYROWAVE_CPU_BUFFER_FORMAT_NV12)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
if (decoder->chroma == ChromaSubsampling::Chroma420 && buffers->format == PYROWAVE_CPU_BUFFER_FORMAT_YUV444P)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
if (decoder->chroma == ChromaSubsampling::Chroma444 && buffers->format != PYROWAVE_CPU_BUFFER_FORMAT_YUV444P)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
|
|
for (int plane = 0; plane < 3; plane++)
|
|
{
|
|
int plane_width = decoder->width;
|
|
int plane_height = decoder->height;
|
|
|
|
if (plane != 0 && decoder->chroma == ChromaSubsampling::Chroma420)
|
|
{
|
|
plane_width /= 2;
|
|
plane_height /= 2;
|
|
}
|
|
|
|
const size_t plane_bpp = 1;
|
|
|
|
if (buffers->row_stride_in_bytes[plane] < plane_width * plane_bpp)
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
if (buffers->row_stride_in_bytes[plane] * plane_height > buffers->plane_size_in_bytes[plane])
|
|
return PYROWAVE_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
for (int plane = 0; plane < 3; plane++)
|
|
{
|
|
auto &img = decoder->planes[plane];
|
|
|
|
if (!img)
|
|
{
|
|
auto info = ImageCreateInfo::immutable_2d_image(buffers->width, buffers->height, VK_FORMAT_R8_UNORM);
|
|
info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
if (decoder->fragment_path)
|
|
{
|
|
info.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
}
|
|
else
|
|
{
|
|
info.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
|
info.initial_layout = VK_IMAGE_LAYOUT_GENERAL;
|
|
info.layout = ImageLayout::General;
|
|
}
|
|
|
|
if (plane != 0 && decoder->chroma == ChromaSubsampling::Chroma420)
|
|
{
|
|
info.width /= 2;
|
|
info.height /= 2;
|
|
}
|
|
|
|
img = device->create_image(info);
|
|
if (!img)
|
|
return PYROWAVE_ERROR_OUT_OF_DEVICE_MEMORY;
|
|
}
|
|
}
|
|
|
|
if (decoder->fragment_path)
|
|
{
|
|
auto cmd = device->request_command_buffer(decoder->pyro_device->queue_type);
|
|
cmd->begin_barrier_batch();
|
|
for (auto &img : decoder->planes)
|
|
{
|
|
cmd->image_barrier(*img, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
|
|
VK_PIPELINE_STAGE_2_COPY_BIT, 0, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
|
|
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);
|
|
}
|
|
cmd->end_barrier_batch();
|
|
|
|
// This just queues up a command buffer, flush only happens when sync objects are signaled.
|
|
device->submit(cmd);
|
|
}
|
|
|
|
pyrowave_gpu_buffers gpu_buffers = {};
|
|
for (int plane = 0; plane < 3; plane++)
|
|
{
|
|
auto &p = gpu_buffers.planes[plane];
|
|
p.image = decoder->planes[plane]->get_image();
|
|
p.width = decoder->planes[plane]->get_width();
|
|
p.height = decoder->planes[plane]->get_height();
|
|
p.image_format = decoder->planes[plane]->get_format();
|
|
p.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
p.swizzle = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
p.view_format = p.image_format;
|
|
p.layout = decoder->fragment_path ? VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL;
|
|
}
|
|
|
|
BufferCreateInfo bufinfo = {};
|
|
BufferHandle readback_buffers[3];
|
|
|
|
auto res = pyrowave_decoder_decode_gpu_buffer(decoder, nullptr, nullptr, &gpu_buffers);
|
|
if (res != PYROWAVE_SUCCESS)
|
|
return res;
|
|
|
|
auto cmd = device->request_command_buffer(decoder->pyro_device->queue_type);
|
|
|
|
if (decoder->fragment_path)
|
|
{
|
|
cmd->begin_barrier_batch();
|
|
for (auto &img : decoder->planes)
|
|
{
|
|
cmd->image_barrier(*img, VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
|
}
|
|
cmd->end_barrier_batch();
|
|
}
|
|
else
|
|
{
|
|
cmd->barrier(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
|
|
VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
|
}
|
|
|
|
for (int plane = 0; plane < 3; plane++)
|
|
{
|
|
bufinfo.size = buffers->plane_size_in_bytes[plane];
|
|
bufinfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
bufinfo.domain = BufferDomain::CachedHost;
|
|
readback_buffers[plane] = device->create_buffer(bufinfo);
|
|
|
|
cmd->copy_image_to_buffer(*readback_buffers[plane], *decoder->planes[plane], 0, {},
|
|
{decoder->planes[plane]->get_width(), decoder->planes[plane]->get_height(), 1},
|
|
buffers->row_stride_in_bytes[plane], 0,
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1});
|
|
}
|
|
|
|
cmd->barrier(VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
|
VK_PIPELINE_STAGE_2_HOST_BIT, VK_ACCESS_2_HOST_READ_BIT);
|
|
|
|
Fence fence;
|
|
device->submit(cmd, &fence);
|
|
fence->wait();
|
|
|
|
for (int plane = 0; plane < 3; plane++)
|
|
{
|
|
void *mapped = device->map_host_buffer(*readback_buffers[plane], MEMORY_ACCESS_READ_BIT);
|
|
memcpy(buffers->data[plane], mapped, buffers->plane_size_in_bytes[plane]);
|
|
}
|
|
|
|
return PYROWAVE_SUCCESS;
|
|
}
|
|
|
|
void pyrowave_decoder_destroy(pyrowave_decoder decoder)
|
|
{
|
|
auto *device = decoder->device;
|
|
Util::set_thread_logging_interface(&null_logger);
|
|
delete decoder;
|
|
device->next_frame_context();
|
|
}
|
|
}
|