4c3b11445c
Phase 0 of design/pyrowave-codec-plan.md — the opt-in wired-LAN ultra-low- latency codec. Vendored at upstream 509e4f88 (API 0.4.0, Granite 44362775, volk + vulkan-headers pins in PUNKTFUNK-VENDOR.txt), pruned to the 6.6 MB the standalone no-renderer build needs; scripts/vendor-pyrowave.sh reproduces the tree (a pin bump is protocol-affecting, plan §4.2). build.rs drives the wrapper CMakeLists (static archives incl. a static C-API lib upstream only ships shared) + bindgen over pyrowave.h; Linux and Windows only, empty stub elsewhere (Apple gets a native Metal port, §4.7). Offline-safe by construction: no network, no system lib, vendored Vulkan headers — same model as the opus dep (flatpak builder has no network). Phase-0 validation on .21 (RTX 5070 Ti, driver 610.43.03): - upstream pyrowave-c-test + interop test (incl. dmabuf/DRM-modifier Vulkan<->Vulkan) pass, from the pristine AND the pruned tree - GPU kernel times at ~1.6 bpp noise: encode/decode 0.090/0.042 ms @800p, 0.146/0.067 @1080p, 0.226/0.103 @1440p, 0.477/0.201 @4K — order of magnitude under NVENC's 1-2 ms retrieve, CBR lands within ~100 B of target - cargo test -p pyrowave-sys green (static link + API-version pin check) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1499 lines
48 KiB
C++
1499 lines
48 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;
|
|
|
|
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;
|
|
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();
|
|
|
|
BufferCreateInfo bufinfo = {};
|
|
bufinfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
|
|
|
bufinfo.size = encoder->encoder.get_meta_required_size();
|
|
bufinfo.domain = BufferDomain::CachedHost;
|
|
encoder->queued_meta = device->create_buffer(bufinfo);
|
|
|
|
if (!encoder->queued_meta)
|
|
return PYROWAVE_ERROR_OUT_OF_HOST_MEMORY;
|
|
|
|
bufinfo.domain = BufferDomain::Device;
|
|
auto queued_meta_gpu = device->create_buffer(bufinfo);
|
|
|
|
if (!queued_meta_gpu)
|
|
return PYROWAVE_ERROR_OUT_OF_DEVICE_MEMORY;
|
|
|
|
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;
|
|
|
|
bufinfo.size = target_bitstream_size + encoder->encoder.get_meta_required_size();
|
|
bufinfo.domain = BufferDomain::CachedHost;
|
|
encoder->queued_bitstream = device->create_buffer(bufinfo);
|
|
|
|
if (!encoder->queued_bitstream)
|
|
return PYROWAVE_ERROR_OUT_OF_HOST_MEMORY;
|
|
|
|
bufinfo.domain = BufferDomain::Device;
|
|
auto queued_bitstream_gpu = device->create_buffer(bufinfo);
|
|
|
|
if (!queued_bitstream_gpu)
|
|
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();
|
|
}
|
|
}
|