-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e76949d
Showing
15 changed files
with
3,204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/Granite | ||
/cmake-build-* | ||
/.idea | ||
*.iml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "vulkan-header"] | ||
path = vulkan-header | ||
url = https://github.com/KhronosGroup/Vulkan-Headers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
### Copyright (c) 2024 Arntzen Software AS | ||
### SPDX-License-Identifier: MIT | ||
|
||
cmake_minimum_required(VERSION 3.20) | ||
set(CMAKE_CXX_STANDARD 14) | ||
set(CMAKE_C_STANDARD 99) | ||
project(pyroenc LANGUAGES CXX C) | ||
|
||
if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")) | ||
set(PYROENC_CXX_FLAGS -Wshadow -Wall -Wextra -Wno-comment -Wno-missing-field-initializers -Wno-empty-body -fvisibility=hidden) | ||
if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") | ||
set(PYROENC_CXX_FLAGS ${PYROENC_CXX_FLAGS} -Wno-backslash-newline-escape) | ||
endif() | ||
if (NOT (${CMAKE_BUILD_TYPE} MATCHES "Release")) | ||
message("Enabling frame pointer for profiling/debug.") | ||
set(PYROENC_CXX_FLAGS ${PYROENC_CXX_FLAGS} -fno-omit-frame-pointer) | ||
endif() | ||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") | ||
message("Enabling SSE3 support.") | ||
set(PYROENC_CXX_FLAGS ${PYROENC_CXX_FLAGS} -msse3) | ||
endif() | ||
elseif (MSVC) | ||
set(PYROENC_CXX_FLAGS /D_CRT_SECURE_NO_WARNINGS /wd4267 /wd4244 /wd4309 /wd4005 /MP) | ||
endif() | ||
|
||
include(GNUInstallDirs) | ||
|
||
if (IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Granite) | ||
# Stripped down Granite build | ||
set(GRANITE_RENDERER OFF CACHE BOOL "" FORCE) | ||
set(GRANITE_VULKAN_SPIRV_CROSS OFF CACHE BOOL "" FORCE) | ||
set(GRANITE_VULKAN_SHADER_MANAGER_RUNTIME_COMPILER OFF CACHE BOOL "" FORCE) | ||
set(GRANITE_VULKAN_FOSSILIZE OFF CACHE BOOL "" FORCE) | ||
set(GRANITE_SHIPPING ON CACHE BOOL "" FORCE) | ||
set(GRANITE_VULKAN_SYSTEM_HANDLES OFF CACHE BOOL "" FORCE) | ||
add_subdirectory(Granite EXCLUDE_FROM_ALL) | ||
message("pyroenc - Found Granite. Enabling test.") | ||
else() | ||
message("pyroenc - Did not find Granite. Disabling build of tests.") | ||
endif() | ||
|
||
if (NOT TARGET Vulkan::Headers) | ||
add_subdirectory(vulkan-header EXCLUDE_FROM_ALL) | ||
endif() | ||
|
||
add_library(pyroenc STATIC pyroenc.cpp pyroenc.hpp pyroenc_vk_table.inl) | ||
target_link_libraries(pyroenc PUBLIC Vulkan::Headers) | ||
target_compile_options(pyroenc PRIVATE ${PYROENC_CXX_FLAGS}) | ||
target_include_directories(pyroenc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) | ||
|
||
if (IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Granite) | ||
add_subdirectory(test) | ||
endif() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 Arntzen Software AS | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
# PyroEnc | ||
|
||
PyroEnc is a simple and standalone library for encoding video with Vulkan video, | ||
suitable for direct integration in a Vulkan application. | ||
It should also be useful as a sample of how to use Vulkan video encoding. | ||
|
||
## Partnership | ||
|
||
Development of this Vulkan video encoding library has been made in partnership with | ||
[3dverse technologies inc](https://3dverse.com/). | ||
|
||
## Device support | ||
|
||
So far, it only works on NVIDIA. The goal is to support every correct Vulkan video | ||
implementation, but RADV and AMD Windows drivers are known to crash. | ||
PyroEnc is actively tested against Vulkan validation layers. | ||
|
||
## Features | ||
|
||
Currently, H.264 encode with a simple I and P GOP structure is supported. | ||
The end goal is to support: | ||
|
||
- H.264 | ||
- H.265 (8-bit / 10-bit / HDR) | ||
- AV1 (if/when that happens) | ||
|
||
The focus of this library is real-time, low-latency streaming, suitable for game streaming. | ||
This focus may extend to high-quality, high-latency encoding as well should | ||
the need arise. | ||
|
||
### Currently supported | ||
|
||
- Take RGB as input (YCbCr conversion happens on GPU) | ||
- Adjust rate control dynamically | ||
- Insert IDR frames on-demand | ||
|
||
### Future features when drivers mature | ||
|
||
- Intra-refresh with multiple slice approach | ||
|
||
## API | ||
|
||
NOTE: The API is not frozen yet and will change as the implementation matures. | ||
|
||
### Creating Encoder | ||
|
||
To create an encoder, various information must be provided, such as: | ||
|
||
- VkInstance | ||
- Must enable Vulkan 1.3 in apiVersion | ||
- VkPhysicalDevice | ||
- VkDevice | ||
- Must support Vulkan 1.3 and have `VK_KHR_push_descriptor` extension enabled | ||
- `VK_KHR_push_descriptor` requirement may be dropped if need be | ||
- Must have video extensions and queues enabled | ||
- Potentially, PyroEnc can provide an interface to help doing this, but currently does not | ||
- vkGetInstanceProcAddr callback | ||
- VkQueue + queue family index for conversion queue and encode queue | ||
- Conversion queue can be either graphics or compute. PyroEnc only uses compute shaders | ||
- Video encoding parameters | ||
- Width | ||
- Height | ||
- Encoding profile | ||
- Frame rate | ||
- Tuning parameters | ||
|
||
``` | ||
#include "pyroenc.hpp" | ||
using namespace PyroEnc; | ||
Encoder encoder; | ||
EncoderCreateInfo info = {}; | ||
encoder.init_encoder(info); | ||
``` | ||
|
||
### Updating rate control | ||
|
||
This can be called at any time and takes effect from next `send_frame`. | ||
VBR, CBR and ConstantQP mode are supported which directly maps to Vulkan video rate control. | ||
It should be called once before the first frame is sent to encoder. | ||
|
||
``` | ||
RateControlInfo info = {}; | ||
encoder.set_rate_control_info(info); | ||
``` | ||
|
||
### Sending frame to be encoded | ||
|
||
Currently, only RGB input is supported. | ||
|
||
``` | ||
FrameInfo info = {}; | ||
info.view = imageView; // Must be UNORM format with usage VK_IMAGE_USAGE_SAMPLED_BIT. | ||
info.width = width; // Must match the encoder. Scaling is currently not supported. | ||
info.height = height; // Must match the encoder. Scaling is currently not supported. | ||
info.pts = pts++; // Not semantically important to PyroEnc, but it is passed back in EncodedFrame. | ||
info.force_idr = ...; // If true, forces IDR frame to be generated, restarting the GOP. | ||
encoder.send_frame(info); | ||
``` | ||
|
||
`Encoder::send_frame()` will call `vkQueueSubmit2`, and the application must ensure | ||
that `Encoder::send_frame()` is not called concurrently with any other uses | ||
of either the conversion VkQueue or encoding VkQueue. It is invalid to | ||
concurrently submit to the same VkQueue in Vulkan. | ||
|
||
For synchronization, the image view's memory must be visible and owned by the queue. | ||
Generally, this will be `VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT` and `VK_ACCESS_2_SHADER_SAMPLED_READ_BIT`, | ||
with image layout `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. `Encoder::get_conversion_dst_stage()` and friends | ||
can be called to determine this. For example, if you were rendering to the image, | ||
and then want to send it to encode. | ||
|
||
``` | ||
VkImageMemoryBarrier2 barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 }; | ||
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; | ||
barrier.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; | ||
barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | ||
barrier.newLayout = encoder.get_conversion_image_layout(); | ||
barrier.dstStageMask = encoder.get_conversion_dst_stage(); | ||
barrier.dstAccessMask = encoder.get_conversion_dst_access(); | ||
// Fill in the rest | ||
``` | ||
|
||
To avoid a write-after-read hazard, wait for `encoder.get_conversion_stage_stage()` to complete | ||
execution. The only operation done on the image is to convert color space to YCbCr, so | ||
it should complete very quickly and not stall the graphics queue for long. | ||
|
||
### Receiving frame | ||
|
||
After sending a frame for encode, you can receive 0 or more frames. | ||
In the current implementation without B-frames, there is a 1:1 correspondence between | ||
sending a frame and receiving it. This API style is similar to FFmpeg. | ||
|
||
``` | ||
EncodedFrame frame; | ||
while (encoder.receive_encoded_frame(frame) == Result::Success) | ||
{ | ||
// The API is async. Wait for frame to complete encode before we can grab bitstream. | ||
frame.wait(); | ||
if (frame.get_status() == VK_QUERY_RESULT_STATUS_COMPLETE_KHR) | ||
{ | ||
// For IDR frames, make sure we write out SPS/PPS as well. | ||
// To avoid additional memory allocations to pack the payloads together, | ||
// let application handle it. | ||
if (frame.is_idr()) | ||
write_packet(encoder.get_encoded_parameters(), encoder.get_encoded_parameters_size()); | ||
write_packet(frame.get_pts(), frame.get_dts(), frame.get_payload(), frame.get_size()); | ||
} | ||
} | ||
``` | ||
|
||
`EncodedFrame` holds ownership of an entry in the encoder frame pool. | ||
If there are too many `EncodedFrame` objects in flight when calling `send_frame()`, | ||
it may return `Result::NotReady`. | ||
Generally, there is little need to go beyond double-buffering the encoded frame. | ||
All `EncodedFrame` objects must be destroyed before destroying the parent `Encoder`. | ||
|
||
## Building | ||
|
||
The project is just a simple single C++ file and single header. | ||
The intended use is a static library. | ||
|
||
``` | ||
add_subdirectory(pyroenc) | ||
target_link_libraries(my-project PRIVATE pyroenc) | ||
``` | ||
|
||
Alternatively, you can just add `pyroenc.cpp` to your build system. | ||
The only include path required is Vulkan headers. | ||
|
||
## Sample | ||
|
||
As a sample demonstrating how to use the API, see the `test/` folder. | ||
The samples are written using Granite to avoid a ton of boilerplate. | ||
Run `checkout_granite.sh` from top folder before invoking CMake. In this case, tests are enabled. | ||
This checkout is not done automatically to avoid bloating the simple nature of this library. | ||
|
||
``` | ||
# Decode 10 seconds of 1080p test video as raw RGBA | ||
ffmpeg -i mytest.mkv -vf scale=1920:1080 -an -t 10 -pix_fmt rgba test.rgb | ||
# Encode raw H.264 at 1920x1080, 60 fps, 15 mbits. The test assumes low-latency CBR encoding. | ||
./pyroenc-test test.h264 test.rgb 1920 1080 60 15000 | ||
# Playback | ||
ffplay test.h264 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/bin/bash | ||
|
||
git clone https://github.com/Themaister/Granite | ||
cd Granite | ||
git submodule update --init |
Oops, something went wrong.