From 665a5336ac263b3038a4e1b22e22c1f148e89a73 Mon Sep 17 00:00:00 2001
From: Jens Koenen <koenen@vr.rwth-aachen.de>
Date: Thu, 3 Nov 2022 17:03:02 +0100
Subject: [PATCH] The Nvidia encoder works now. But still some clean up
 required.

---
 src/encoder/encoder.cpp          |  55 ++++
 src/encoder/encoder.hpp          |  69 +++--
 src/encoder/nvidia_encoder.cpp   | 501 +++++++++++++++++++++++--------
 src/encoder/nvidia_encoder.hpp   |  32 +-
 src/encoder/vulkan_encoder.cpp   |  19 +-
 src/encoder/vulkan_encoder.hpp   |  11 +-
 src/headset/emulated_headset.cpp |  26 +-
 src/headset/remote_headset.cpp   |   5 +-
 8 files changed, 542 insertions(+), 176 deletions(-)

diff --git a/src/encoder/encoder.cpp b/src/encoder/encoder.cpp
index f86d60e4..99f7d1aa 100644
--- a/src/encoder/encoder.cpp
+++ b/src/encoder/encoder.cpp
@@ -2,6 +2,61 @@
 #include "vulkan_encoder.hpp"
 #include "nvidia_encoder.hpp"
 
+void Encoder::set_mode(EncoderMode mode)
+{
+
+}
+
+void Encoder::set_quality(double quality)
+{
+
+}
+
+void Encoder::set_bitrate(double bitrate)
+{
+
+}
+
+void Encoder::set_key_rate(uint32_t key_rate)
+{
+
+}
+
+void Encoder::set_frame_rate(uint32_t frame_rate)
+{
+
+}
+
+EncoderMode Encoder::get_mode() const
+{
+    return (EncoderMode)0;
+}
+
+double Encoder::get_quality() const
+{
+    return 0.0;
+}
+
+double Encoder::get_bitrate() const
+{
+    return 0.0;
+}
+
+uint32_t Encoder::get_key_rate() const
+{
+    return 0;
+}
+
+uint32_t Encoder::get_frame_rate() const
+{
+    return 0;
+}
+
+bool Encoder::is_supported(EncoderSetting setting) const
+{
+    return false;
+}
+
 bool setup_instance_for_encoder(EncoderType encoder_type, lava::frame_config& config)
 {
     switch (encoder_type)
diff --git a/src/encoder/encoder.hpp b/src/encoder/encoder.hpp
index d7e4c414..2968eaad 100644
--- a/src/encoder/encoder.hpp
+++ b/src/encoder/encoder.hpp
@@ -41,12 +41,30 @@ enum EncoderMode
     ENCODER_MODE_CONSTANT_QUALITY
 };
 
+// Supported formats which can be used as input for the encoder.
+enum EncoderFormat
+{
+    ENCODER_FORMAT_R8G8B8A8_SRGB = VK_FORMAT_R8G8B8A8_SRGB,
+    ENCODER_FORMAT_B8G8R8A8_SRGB = VK_FORMAT_B8G8R8A8_SRGB
+};
+
+// Settings that the encoder could provide.
+enum EncoderSetting
+{
+    ENCODER_SETTING_MODE_CONSTANT_BITRATE,
+    ENCODER_SETTING_MODE_CONSTANT_QUALITY,
+    ENCODER_SETTING_QUALITY,
+    ENCODER_SETTING_BITRATE,
+    ENCODER_SETTING_KEY_RATE,
+    ENCODER_SETTING_FRAME_RATE
+};
+
 // When adding or removing an encoder, the functions make_encoder(...), setup_instance_for_encoder(...), setup_device_for_encoder(...) as well as
 // the function set_encoder(...) of the CommandParser need to be adapted accordingly.
 enum EncoderType
 {
-    ENCODER_TYPE_VULKAN, //Use vulkan video for encoding
-    ENCODER_TYPE_NVIDIA  //Use Nvenc for encoding
+    ENCODER_TYPE_VULKAN, // Use vulkan video for encoding
+    ENCODER_TYPE_NVIDIA  // Use Nvenc for encoding
 };
 
 class Encoder
@@ -54,7 +72,7 @@ class Encoder
 public:
     typedef std::shared_ptr<Encoder> Ptr;
 
-    //NOTE: If the is_config flag is true, the content passed to this callback function contains a sequence parameter set or picture parameter set.
+    // If the is_config flag is true, the content passed to this callback function contains a sequence parameter set or picture parameter set.
     typedef std::function<void(const std::span<uint8_t>& content, bool is_config)> OnEncodeComplete;
     typedef std::function<void()> OnEncodeError;
 
@@ -62,34 +80,35 @@ public:
     Encoder() = default;
     virtual ~Encoder() = default;
 
-    //NOTE: The parameter input_buffers defines how many input images the encoder should provide.
-    virtual bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t input_buffers) = 0;
+    virtual bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, EncoderFormat format) = 0;
     virtual void destroy() = 0;
 
-    //NOTE: The following functions should be thread safe.
-    //NOTE: The callback function is executed only by the worker thread of the encoder.
+    // The following functions should be thread safe.
+    // The callback function is executed only by the worker thread of the encoder.
+    // The format of the image needs to be the same as the format that was specified during the creation of the encoder.
     virtual bool encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function) = 0;
 
-    //NOTE: The callbacks are executed only by the worker thread of the encoder.
-    //NOTE: Set the callbacks before calling create and don't do it again after calling create.
+    // The callbacks are executed only by the worker thread of the encoder.
+    // Set the callbacks before calling create and don't do it again after calling create.
     virtual void set_on_encode_error(OnEncodeError function) = 0;
 
-    //NOTE: The following functions should be thread safe.
-    //NOTE: The following settings are only suggestions and can be ignored if for example the feature is not supported.
-    virtual void set_mode(EncoderMode mode) = 0;            //NOTE: Defines if the encoder should use constant quality or constant bitrate. Default: constant quality
-    virtual void set_quality(double quality) = 0;           //NOTE: The quality ranges from 0.0 (low quality) to 1.0 (high quality). Default: 0.0
-    virtual void set_bitrate(double bitrate) = 0;           //NOTE: The bitrate is given in MBits/s. Default: 5.0
-    virtual void set_key_rate(uint32_t key_rate) = 0;       //NOTE: The keyrate is defined as the number of frames between two i-frames. Default: 90
-    virtual void set_frame_rate(uint32_t frame_rate) = 0;   //NOTE: The frame rate is given in frames per second. Default: 90
-
-    //NOTE: The following functions should be thread safe.
-    virtual EncoderMode get_mode() const = 0;
-    virtual double get_quality() const = 0;
-    virtual double get_bitrate() const = 0;
-    virtual uint32_t get_key_rate() const = 0;
-    virtual uint32_t get_frame_rate() const = 0;
-
-    //TODO: Add functions with which it is possible to query whether a quality parameter is supported or not.
+    // The following functions should be thread safe.
+    // The following settings are only suggestions and can be ignored if for example the feature is not supported.
+    virtual void set_mode(EncoderMode mode);            // Defines if the encoder should use constant quality or constant bitrate. Default: constant quality
+    virtual void set_quality(double quality);           // The quality ranges from 0.0 (low quality) to 1.0 (high quality).        Default: 0.0
+    virtual void set_bitrate(double bitrate);           // The bitrate is given in MBits/s.                                        Default: 5.0
+    virtual void set_key_rate(uint32_t key_rate);       // The keyrate is defined as the number of frames between two i-frames.    Default: 90
+    virtual void set_frame_rate(uint32_t frame_rate);   // The frame rate is given in frames per second.                           Default: 90
+
+    // The following functions should be thread safe.
+    virtual EncoderMode get_mode() const;
+    virtual double get_quality() const;
+    virtual double get_bitrate() const;
+    virtual uint32_t get_key_rate() const;
+    virtual uint32_t get_frame_rate() const;
+
+    // The following function can be used to checker which of the settings listed above will be respected by the encoder.
+    virtual bool is_supported(EncoderSetting setting) const;
 };
 
 bool setup_instance_for_encoder(EncoderType encoder_type, lava::frame_config& config);
diff --git a/src/encoder/nvidia_encoder.cpp b/src/encoder/nvidia_encoder.cpp
index 399da2c2..5f90d854 100644
--- a/src/encoder/nvidia_encoder.cpp
+++ b/src/encoder/nvidia_encoder.cpp
@@ -22,7 +22,12 @@ NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance_Func = nullptr;
 PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR_Func = nullptr;
 PFN_vkGetSemaphoreWin32HandleKHR vkGetSemaphoreWin32HandleKHR_Func = nullptr;
 
-bool NvidiaEncoder::create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t input_buffers)
+NvidiaEncoder::NvidiaEncoder() : worker_pool(1)
+{
+
+}
+
+bool NvidiaEncoder::create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, EncoderFormat format)
 {
     if (!this->create_context(device))
     {
@@ -34,11 +39,11 @@ bool NvidiaEncoder::create(lava::device_ptr device, const lava::renderer& render
         return false;
     }
 
-    for (uint32_t index = 0; index < input_buffers; index++)
+    for (uint32_t index = 0; index < NVIDIA_ENCODER_FRAMES; index++)
     {
         NvidiaEncoderFrame::Ptr frame = std::make_shared<NvidiaEncoderFrame>();
 
-        if (!this->create_input_buffer(frame, device, size))
+        if (!this->create_input_buffer(frame, device, size, format))
         {
             return false;
         }
@@ -57,18 +62,18 @@ bool NvidiaEncoder::create(lava::device_ptr device, const lava::renderer& render
         {
             return false;
         }
-    }
 
-    this->worker_thread = std::thread([this]()
-    {
-        this->worker_executor.run();
-    });
+        this->frame_queue.push_back(frame);
+        this->frame_list.push_back(frame);
+    }
 
     return true;
 }
 
 void NvidiaEncoder::destroy()
 {
+    this->worker_pool.stop();
+
     for (NvidiaEncoderFrame::Ptr frame : this->frame_list)
     {
         this->destroy_frame(frame);
@@ -90,6 +95,20 @@ bool NvidiaEncoder::encode(VkCommandBuffer command_buffer, lava::renderer& rende
         return false;
     }
 
+    frame->output_parameter = this->parameter_change;
+    frame->on_encode_complete = std::move(function);
+
+    if (this->config_change)
+    {
+        /*if (!this->apply_config())
+        {
+            return false;
+        }*/
+    }
+
+    this->parameter_change = false;
+    this->config_change = false;
+
     std::vector<VkImageMemoryBarrier> begin_barriers;
 
     if (image_layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
@@ -128,74 +147,54 @@ bool NvidiaEncoder::encode(VkCommandBuffer command_buffer, lava::renderer& rende
 
     vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, begin_barriers.size(), begin_barriers.data());
 
+    VkImageCopy image_copy;
+    image_copy.srcSubresource = image->get_subresource_layers();
+    image_copy.srcOffset.x = 0;
+    image_copy.srcOffset.y = 0;
+    image_copy.srcOffset.z = 0;
+    image_copy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    image_copy.dstSubresource.mipLevel = 0;
+    image_copy.dstSubresource.baseArrayLayer = 0;
+    image_copy.dstSubresource.layerCount = 1;
+    image_copy.dstOffset.x = 0;
+    image_copy.dstOffset.y = 0;
+    image_copy.dstOffset.z = 0;
+    image_copy.extent.width = frame->image_size.x;
+    image_copy.extent.height = frame->image_size.y;
+    image_copy.extent.depth = 1;
+    
+    vkCmdCopyImage(command_buffer, image->get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, frame->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
 
+    std::vector<VkImageMemoryBarrier> end_barriers;
 
+    if (image_layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
+    {
+        VkImageMemoryBarrier& image_barrier = end_barriers.emplace_back();
+        image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+        image_barrier.pNext = nullptr;
+        image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+        image_barrier.dstAccessMask = 0;
+        image_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+        image_barrier.newLayout = image_layout;
+        image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        image_barrier.image = image->get();
+        image_barrier.subresourceRange = image->get_subresource_range();
+    }
 
+    VkImageMemoryBarrier& input_end_barrier = end_barriers.emplace_back();
+    input_end_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    input_end_barrier.pNext = nullptr;
+    input_end_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    input_end_barrier.dstAccessMask = 0;
+    input_end_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    input_end_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+    input_end_barrier.srcQueueFamilyIndex = renderer.get_queue().family;
+    input_end_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
+    input_end_barrier.image = frame->image;
+    input_end_barrier.subresourceRange = subresource_range;
 
-
-
-    vkCmdBlitImage(command_buffer, );
-
-    vkCmdPipelineBarrier();
-
-
-    cuWaitExternalSemaphoresAsync();
-
-    NV_ENC_PIC_PARAMS encode_parameters;
-    uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_PIC_PARAMS_VER. */
-    uint32_t inputWidth; /**< [in]: Specifies the input frame width */
-    uint32_t inputHeight; /**< [in]: Specifies the input frame height */
-    uint32_t inputPitch; /**< [in]: Specifies the input buffer pitch. If pitch value is not known, set this to inputWidth. */
-    uint32_t encodePicFlags; /**< [in]: Specifies bit-wise OR of encode picture flags. See ::NV_ENC_PIC_FLAGS enum. */
-    uint32_t frameIdx; /**< [in]: Specifies the frame index associated with the input frame [optional]. */
-    uint64_t inputTimeStamp; /**< [in]: Specifies opaque data which is associated with the encoded frame, but not actually encoded in the output bitstream.
-                                                                                           This opaque data can be used later to uniquely refer to the corresponding encoded frame. For example, it can be used
-                                                                                           for identifying the frame to be invalidated in the reference picture buffer, if lost at the client. */
-    uint64_t inputDuration; /**< [in]: Specifies duration of the input picture */
-    NV_ENC_INPUT_PTR inputBuffer; /**< [in]: Specifies the input buffer pointer. Client must use a pointer obtained from ::NvEncCreateInputBuffer() or ::NvEncMapInputResource() APIs.*/
-    NV_ENC_OUTPUT_PTR outputBitstream; /**< [in]: Specifies the output buffer pointer.
-                                                                                           If NV_ENC_INITIALIZE_PARAMS::enableOutputInVidmem is set to 0, specifies the pointer to output buffer. Client should use a pointer obtained from ::NvEncCreateBitstreamBuffer() API.
-                                                                                           If NV_ENC_INITIALIZE_PARAMS::enableOutputInVidmem is set to 1, client should allocate buffer in video memory for NV_ENC_ENCODE_OUT_PARAMS struct and encoded bitstream data. Client
-                                                                                           should use a pointer obtained from ::NvEncMapInputResource() API, when mapping this output buffer and assign it to NV_ENC_PIC_PARAMS::outputBitstream.
-                                                                                           First 256 bytes of this buffer should be interpreted as NV_ENC_ENCODE_OUT_PARAMS struct followed by encoded bitstream data. Recommended size for output buffer is sum of size of
-                                                                                           NV_ENC_ENCODE_OUT_PARAMS struct and twice the input frame size for lower resolution eg. CIF and 1.5 times the input frame size for higher resolutions. If encoded bitstream size is
-                                                                                           greater than the allocated buffer size for encoded bitstream, then the output buffer will have encoded bitstream data equal to buffer size. All CUDA operations on this buffer must use
-                                                                                           the default stream. */
-    void* completionEvent; /**< [in]: Specifies an event to be signaled on completion of encoding of this Frame [only if operating in Asynchronous mode]. Each output buffer should be associated with a distinct event pointer. */
-    NV_ENC_BUFFER_FORMAT bufferFmt; /**< [in]: Specifies the input buffer format. */
-    NV_ENC_PIC_STRUCT pictureStruct; /**< [in]: Specifies structure of the input picture. */
-    NV_ENC_PIC_TYPE pictureType; /**< [in]: Specifies input picture type. Client required to be set explicitly by the client if the client has not set NV_ENC_INITALIZE_PARAMS::enablePTD to 1 while calling NvInitializeEncoder. */
-    NV_ENC_CODEC_PIC_PARAMS codecPicParams; /**< [in]: Specifies the codec specific per-picture encoding parameters. */
-    NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE meHintCountsPerBlock[2]; /**< [in]: For H264 and Hevc, specifies the number of hint candidates per block per direction for the current frame. meHintCountsPerBlock[0] is for L0 predictors and meHintCountsPerBlock[1] is for L1 predictors.
-                                                                                           The candidate count in NV_ENC_PIC_PARAMS::meHintCountsPerBlock[lx] must never exceed NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[lx] provided during encoder initialization. */
-    NVENC_EXTERNAL_ME_HINT* meExternalHints; /**< [in]: For H264 and Hevc, Specifies the pointer to ME external hints for the current frame. The size of ME hint buffer should be equal to number of macroblocks * the total number of candidates per macroblock.
-                                                                                           The total number of candidates per MB per direction = 1*meHintCountsPerBlock[Lx].numCandsPerBlk16x16 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk16x8 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk8x8
-                                                                                           + 4*meHintCountsPerBlock[Lx].numCandsPerBlk8x8. For frames using bidirectional ME , the total number of candidates for single macroblock is sum of total number of candidates per MB for each direction (L0 and L1) */
-    uint32_t reserved1[6]; /**< [in]: Reserved and must be set to 0 */
-    void* reserved2[2]; /**< [in]: Reserved and must be set to NULL */
-    int8_t* qpDeltaMap; /**< [in]: Specifies the pointer to signed byte array containing value per MB for H264 and per CTB for HEVC in raster scan order for the current picture, which will be interpreted depending on NV_ENC_RC_PARAMS::qpMapMode.
-                                                                                            If NV_ENC_RC_PARAMS::qpMapMode is NV_ENC_QP_MAP_DELTA, qpDeltaMap specifies QP modifier per MB for H264 and per CTB for HEVC. This QP modifier will be applied on top of the QP chosen by rate control.
-                                                                                            If NV_ENC_RC_PARAMS::qpMapMode is NV_ENC_QP_MAP_EMPHASIS, qpDeltaMap specifies Emphasis Level Map per MB for H264. This level value along with QP chosen by rate control is used to
-                                                                                            compute the QP modifier, which in turn is applied on top of QP chosen by rate control.
-                                                                                            If NV_ENC_RC_PARAMS::qpMapMode is NV_ENC_QP_MAP_DISABLED, value in qpDeltaMap will be ignored.*/
-    uint32_t qpDeltaMapSize; /**< [in]: Specifies the size in bytes of qpDeltaMap surface allocated by client and pointed to by NV_ENC_PIC_PARAMS::qpDeltaMap. Surface (array) should be picWidthInMbs * picHeightInMbs for H264 and picWidthInCtbs * picHeightInCtbs for HEVC */
-    uint32_t reservedBitFields; /**< [in]: Reserved bitfields and must be set to 0 */
-    uint16_t meHintRefPicDist[2]; /**< [in]: Specifies temporal distance for reference picture (NVENC_EXTERNAL_ME_HINT::refidx = 0) used during external ME with NV_ENC_INITALIZE_PARAMS::enablePTD = 1 . meHintRefPicDist[0] is for L0 hints and meHintRefPicDist[1] is for L1 hints.
-                                                                                            If not set, will internally infer distance of 1. Ignored for NV_ENC_INITALIZE_PARAMS::enablePTD = 0 */
-    NV_ENC_INPUT_PTR alphaBuffer; /**< [in]: Specifies the input alpha buffer pointer. Client must use a pointer obtained from ::NvEncCreateInputBuffer() or ::NvEncMapInputResource() APIs.
-                                                                                            Applicable only when encoding hevc with alpha layer is enabled. */
-    uint32_t reserved3[286]; /**< [in]: Reserved and must be set to 0 */
-    void* reserved4[59]; /**< [in]: Reserved and must be set to NULL */
-
-
-
-
-    nvenc_functions.nvEncEncodePicture(this->nvenc_session, )
-
-
-
-
-
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, end_barriers.size(), end_barriers.data());
 
     this->submit_frame(frame, renderer);
 
@@ -210,26 +209,25 @@ void NvidiaEncoder::set_on_encode_error(OnEncodeError function)
 void NvidiaEncoder::set_mode(EncoderMode mode)
 {
     this->mode = mode;
+    this->config_change = true;
 }
 
 void NvidiaEncoder::set_quality(double quality)
 {
     this->quality = quality;
+    this->config_change = true;
 }
 
 void NvidiaEncoder::set_bitrate(double bitrate)
 {
     this->bitrate = bitrate;
-}
-
-void NvidiaEncoder::set_key_rate(uint32_t key_rate)
-{
-    this->key_rate = key_rate;
+    this->config_change = true;
 }
 
 void NvidiaEncoder::set_frame_rate(uint32_t frame_rate)
 {
     this->frame_rate = frame_rate;
+    this->config_change = true;
 }
 
 EncoderMode NvidiaEncoder::get_mode() const
@@ -247,14 +245,19 @@ double NvidiaEncoder::get_bitrate() const
     return this->bitrate;
 }
 
-uint32_t NvidiaEncoder::get_key_rate() const
+uint32_t NvidiaEncoder::get_frame_rate() const
 {
-    return this->key_rate;
+    return this->frame_rate;
 }
 
-uint32_t NvidiaEncoder::get_frame_rate() const
+bool NvidiaEncoder::is_supported(EncoderSetting setting) const
 {
-    return this->frame_rate;
+    if (setting == ENCODER_SETTING_KEY_RATE)
+    {
+        return false;
+    }
+
+    return true;
 }
 
 bool NvidiaEncoder::aquire_frame(NvidiaEncoderFrame::Ptr& frame)
@@ -281,6 +284,12 @@ void NvidiaEncoder::release_frame(NvidiaEncoderFrame::Ptr frame)
 
 void NvidiaEncoder::submit_frame(NvidiaEncoderFrame::Ptr frame, lava::renderer& renderer)
 {
+#if defined(_WIN32)
+    ResetEvent((HANDLE)frame->event_handle);
+#else
+    #error "Not implemented for this platform!"
+#endif
+
     frame->async_event->async_wait([this, frame](const asio::error_code& error_code)
     {
         if (error_code)
@@ -298,20 +307,230 @@ void NvidiaEncoder::submit_frame(NvidiaEncoderFrame::Ptr frame, lava::renderer&
 
     lava::frame_submission submission;
     submission.semaphore = frame->semaphore;
+    submission.callback = [this, frame]()
+    {
+        this->submit_encode_task(frame);
+    };
 
     renderer.add_submission(submission);
 }
 
 void NvidiaEncoder::read_frame(NvidiaEncoderFrame::Ptr frame)
 {
+    if (frame->output_parameter)
+    {
+        std::array<uint8_t, 512> parameter_buffer;
+        uint32_t parameter_size = 0;
+
+        NV_ENC_SEQUENCE_PARAM_PAYLOAD parameter_info;
+        parameter_info.version = NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER;
+        parameter_info.inBufferSize = parameter_buffer.size();
+        parameter_info.spsId = 0;
+        parameter_info.ppsId = 0;
+        parameter_info.spsppsBuffer = parameter_buffer.data();
+        parameter_info.outSPSPPSPayloadSize = &parameter_size;
+        memset(parameter_info.reserved, 0, sizeof(parameter_info.reserved));
+        memset(parameter_info.reserved2, 0, sizeof(parameter_info.reserved2));
+
+        if (nvenc_functions.nvEncGetSequenceParams(this->nvenc_session, &parameter_info) != NV_ENC_SUCCESS)
+        {
+            this->on_encode_error();
+
+            return;
+        }
+
+        frame->on_encode_complete(std::span(parameter_buffer.data(), parameter_size), true);
+    }
+
+    NV_ENC_LOCK_BITSTREAM lock_stream;
+    memset(&lock_stream, 0, sizeof(lock_stream));
+    lock_stream.version = NV_ENC_LOCK_BITSTREAM_VER;
+    lock_stream.doNotWait = 0;
+    lock_stream.getRCStats = 0;
+    lock_stream.reservedBitFields = 0;
+    lock_stream.outputBitstream = frame->nvenc_output_buffer;
+    lock_stream.sliceOffsets = nullptr;
+    memset(lock_stream.reserved, 0, sizeof(lock_stream.reserved));
+    memset(lock_stream.reserved1, 0, sizeof(lock_stream.reserved1));
+    memset(lock_stream.reserved2, 0, sizeof(lock_stream.reserved2));
+
+    if (nvenc_functions.nvEncLockBitstream(this->nvenc_session, &lock_stream) != NV_ENC_SUCCESS)
+    {
+        this->on_encode_error();
+
+        return;
+    }
+
+    frame->on_encode_complete(std::span((uint8_t*)lock_stream.bitstreamBufferPtr, lock_stream.bitstreamSizeInBytes), false);
+
+    if (nvenc_functions.nvEncUnlockBitstream(this->nvenc_session, frame->nvenc_output_buffer) != NV_ENC_SUCCESS)
+    {
+        this->on_encode_error();
+
+        return;
+    }
+
+    if (nvenc_functions.nvEncUnmapInputResource(this->nvenc_session, frame->nvenc_mapped_buffer) != NV_ENC_SUCCESS)
+    {
+        this->on_encode_error();
+    
+        return;
+    }
+}
+
+bool NvidiaEncoder::apply_config()
+{    
+    if (this->mode == ENCODER_MODE_CONSTANT_QUALITY)
+    {
+        
+    }
+
+    else if (this->mode == ENCODER_MODE_CONSTANT_BITRATE)
+    {
+
+    }
+
+    else
+    {
+        lava::log()->error("Unsupported encoder mode!");
+
+        return false;
+    }
+
+    NV_ENC_RECONFIGURE_PARAMS reconfig;
+    reconfig.version = NV_ENC_RECONFIGURE_PARAMS_VER;
+    reconfig.reInitEncodeParams = this->nvenc_session_config;
+    reconfig.resetEncoder = 1;
+    reconfig.forceIDR = 1;
+    reconfig.reserved = 0;
+
+    if (nvenc_functions.nvEncReconfigureEncoder(this->nvenc_session, &reconfig) != NV_ENC_SUCCESS)
+    {
+        return false;
+    }
+
+    return true;
+}
+
+void NvidiaEncoder::submit_encode_task(NvidiaEncoderFrame::Ptr frame)
+{
+    if (cuCtxPushCurrent(this->cuda_context) != CUDA_SUCCESS)
+    {
+        this->on_encode_error();
 
+        return;
+    }
 
+    CUDA_EXTERNAL_SEMAPHORE_WAIT_PARAMS wait_parameters;
+    memset(&wait_parameters.params, 0, sizeof(wait_parameters.params));
+    wait_parameters.flags = 0;
+    memset(wait_parameters.reserved, 0, sizeof(wait_parameters.reserved));
+    
+    if (cuWaitExternalSemaphoresAsync(&frame->cuda_external_semaphore, &wait_parameters, 1, 0) != CUDA_SUCCESS)
+    {
+        this->on_encode_error();
 
+        return;
+    }
 
+    NV_ENC_MAP_INPUT_RESOURCE map_info;
+    map_info.version = NV_ENC_MAP_INPUT_RESOURCE_VER;
+    map_info.subResourceIndex = 0;
+    map_info.inputResource = 0;
+    map_info.registeredResource = frame->nvenc_input_buffer;
+    map_info.mappedResource = nullptr;
+    map_info.mappedBufferFmt = (NV_ENC_BUFFER_FORMAT)0;
+    memset(map_info.reserved1, 0, sizeof(map_info.reserved1));
+    memset(map_info.reserved2, 0, sizeof(map_info.reserved2));
+
+    if (nvenc_functions.nvEncMapInputResource(this->nvenc_session, &map_info) != NV_ENC_SUCCESS)
+    {
+        this->on_encode_error();
 
+        return;
+    }
 
+    frame->nvenc_mapped_buffer = map_info.mappedResource;
 
+    if (cuCtxPopCurrent(&this->cuda_context) != CUDA_SUCCESS)
+    {
+        this->on_encode_error();
 
+        return;
+    }
+
+    NV_ENC_PIC_PARAMS_MVC motion_vector_parameters;
+    motion_vector_parameters.version = NV_ENC_PIC_PARAMS_MVC_VER;
+    motion_vector_parameters.viewID = 0;
+    motion_vector_parameters.temporalID = 0;
+    motion_vector_parameters.priorityID = 0;
+    memset(motion_vector_parameters.reserved1, 0, sizeof(motion_vector_parameters.reserved1));
+    memset(motion_vector_parameters.reserved2, 0, sizeof(motion_vector_parameters.reserved2));
+
+    NV_ENC_PIC_PARAMS_H264_EXT codec_extention_parameters;
+    codec_extention_parameters.mvcPicParams = motion_vector_parameters;
+    memset(codec_extention_parameters.reserved1, 0, sizeof(codec_extention_parameters.reserved1));
+    
+    NV_ENC_PIC_PARAMS_H264 codec_parameters;
+    codec_parameters.displayPOCSyntax = 0;
+    codec_parameters.reserved3 = 0;
+    codec_parameters.refPicFlag = 0;
+    codec_parameters.colourPlaneId = 0;
+    codec_parameters.forceIntraRefreshWithFrameCnt = 0;
+    codec_parameters.constrainedFrame = 1;
+    codec_parameters.sliceModeDataUpdate = 1;
+    codec_parameters.ltrMarkFrame = 0;
+    codec_parameters.ltrUseFrames = 0;
+    codec_parameters.reservedBitFields = 0;
+    codec_parameters.sliceTypeData = 0;
+    codec_parameters.sliceTypeArrayCnt = 0;
+    codec_parameters.seiPayloadArrayCnt = 0;
+    codec_parameters.seiPayloadArray = nullptr;
+    codec_parameters.sliceMode = 3;
+    codec_parameters.sliceModeData = 4;
+    codec_parameters.ltrMarkFrameIdx = 0;
+    codec_parameters.ltrUseFrameBitmap = 0;
+    codec_parameters.ltrUsageMode = 0;
+    codec_parameters.forceIntraSliceCount = 0;
+    codec_parameters.forceIntraSliceIdx = 0;
+    codec_parameters.h264ExtPicParams = codec_extention_parameters;
+    memset(codec_parameters.reserved, 0, sizeof(codec_parameters.reserved));
+    memset(codec_parameters.reserved2, 0, sizeof(codec_parameters.reserved2));
+
+    NV_ENC_PIC_PARAMS encode_parameters;
+    encode_parameters.version = NV_ENC_PIC_PARAMS_VER;
+    encode_parameters.inputWidth = frame->image_size.x;
+    encode_parameters.inputHeight = frame->image_size.y;
+    encode_parameters.inputPitch = frame->image_layout.rowPitch;
+    encode_parameters.encodePicFlags = 0;
+    encode_parameters.frameIdx = 0;
+    encode_parameters.inputTimeStamp = 0;
+    encode_parameters.inputDuration = 0;
+    encode_parameters.inputBuffer = frame->nvenc_mapped_buffer;
+    encode_parameters.outputBitstream = frame->nvenc_output_buffer;
+    encode_parameters.completionEvent = frame->event_handle;
+    encode_parameters.bufferFmt = map_info.mappedBufferFmt;
+    encode_parameters.pictureStruct = NV_ENC_PIC_STRUCT_FRAME;
+    encode_parameters.pictureType = (NV_ENC_PIC_TYPE)0;
+    encode_parameters.codecPicParams.h264PicParams = codec_parameters;
+    memset(encode_parameters.meHintCountsPerBlock, 0, sizeof(encode_parameters.meHintCountsPerBlock));
+    encode_parameters.meExternalHints = nullptr;
+    memset(encode_parameters.reserved1, 0, sizeof(encode_parameters.reserved1));
+    memset(encode_parameters.reserved2, 0, sizeof(encode_parameters.reserved2));
+    encode_parameters.qpDeltaMap = nullptr;
+    encode_parameters.qpDeltaMapSize = 0;
+    encode_parameters.reservedBitFields = 0;
+    memset(encode_parameters.meHintRefPicDist, 0, sizeof(encode_parameters.meHintRefPicDist));
+    encode_parameters.alphaBuffer = nullptr;
+    memset(encode_parameters.reserved3, 0, sizeof(encode_parameters.reserved3));
+    memset(encode_parameters.reserved4, 0, sizeof(encode_parameters.reserved4));
+
+    if (nvenc_functions.nvEncEncodePicture(this->nvenc_session, &encode_parameters) != NV_ENC_SUCCESS)
+    {
+        this->on_encode_error();
+
+        return;
+    }
 }
 
 bool NvidiaEncoder::create_context(lava::device_ptr device)
@@ -440,41 +659,45 @@ bool NvidiaEncoder::create_session(const glm::uvec2& size)
         return false;
     }
 
-    NV_ENC_CONFIG encode_config = preset_config.presetCfg;
-    encode_config.profileGUID = NV_ENC_H264_PROFILE_HIGH_GUID;
-    encode_config.rcParams.version = NV_ENC_RC_PARAMS_VER;
-
-    NV_ENC_INITIALIZE_PARAMS session_config;
-    session_config.version = NV_ENC_INITIALIZE_PARAMS_VER;
-    session_config.encodeGUID = NV_ENC_CODEC_H264_GUID;
-    session_config.presetGUID = NV_ENC_PRESET_P1_GUID;
-    session_config.encodeWidth = size.x;
-    session_config.encodeHeight = size.y;
-    session_config.darWidth = size.x;
-    session_config.darHeight = size.y;
-    session_config.frameRateNum = this->frame_rate;
-    session_config.frameRateDen = 1;
-    session_config.enableEncodeAsync = 1;
-    session_config.enablePTD = 1;
-    session_config.reportSliceOffsets = 0;
-    session_config.enableSubFrameWrite = 0;
-    session_config.enableExternalMEHints = 0;
-    session_config.enableMEOnlyMode = 0;
-    session_config.enableWeightedPrediction = 0;
-    session_config.enableOutputInVidmem  = 0;
-    session_config.reservedBitFields = 0;
-    session_config.privDataSize = 0;
-    session_config.privData = nullptr;
-    session_config.encodeConfig = &encode_config;
-    session_config.maxEncodeWidth = size.x;
-    session_config.maxEncodeHeight = size.x;
-    memset(session_config.maxMEHintCountsPerBlock, 0, sizeof(session_config.maxMEHintCountsPerBlock));
-    session_config.tuningInfo = NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY;
-    session_config.bufferFormat = (NV_ENC_BUFFER_FORMAT)0;
-    memset(session_config.reserved, 0, sizeof(session_config.reserved));
-    memset(session_config.reserved2, 0, sizeof(session_config.reserved2));
+    this->nvenc_encode_config = preset_config.presetCfg;
+    this->nvenc_encode_config.profileGUID = NV_ENC_H264_PROFILE_HIGH_GUID;
+    this->nvenc_encode_config.rcParams.version = NV_ENC_RC_PARAMS_VER;
+    this->nvenc_encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR;
+    this->nvenc_encode_config.rcParams.averageBitRate = (uint32_t)(this->bitrate * 1000000.0);
+    this->nvenc_encode_config.rcParams.maxBitRate = this->nvenc_encode_config.rcParams.averageBitRate;
+    this->nvenc_encode_config.rcParams.vbvBufferSize = (uint32_t)(this->nvenc_encode_config.rcParams.averageBitRate * (1.0f / this->frame_rate)) * 5;
+    this->nvenc_encode_config.rcParams.vbvInitialDelay = this->nvenc_encode_config.rcParams.vbvBufferSize;
+
+    this->nvenc_session_config.version = NV_ENC_INITIALIZE_PARAMS_VER;
+    this->nvenc_session_config.encodeGUID = NV_ENC_CODEC_H264_GUID;
+    this->nvenc_session_config.presetGUID = NV_ENC_PRESET_P1_GUID;
+    this->nvenc_session_config.encodeWidth = size.x;
+    this->nvenc_session_config.encodeHeight = size.y;
+    this->nvenc_session_config.darWidth = size.x;
+    this->nvenc_session_config.darHeight = size.y;
+    this->nvenc_session_config.frameRateNum = this->frame_rate;
+    this->nvenc_session_config.frameRateDen = 1;
+    this->nvenc_session_config.enableEncodeAsync = 1;
+    this->nvenc_session_config.enablePTD = 1;
+    this->nvenc_session_config.reportSliceOffsets = 0;
+    this->nvenc_session_config.enableSubFrameWrite = 0;
+    this->nvenc_session_config.enableExternalMEHints = 0;
+    this->nvenc_session_config.enableMEOnlyMode = 0;
+    this->nvenc_session_config.enableWeightedPrediction = 0;
+    this->nvenc_session_config.enableOutputInVidmem  = 0;
+    this->nvenc_session_config.reservedBitFields = 0;
+    this->nvenc_session_config.privDataSize = 0;
+    this->nvenc_session_config.privData = nullptr;
+    this->nvenc_session_config.encodeConfig = &this->nvenc_encode_config;
+    this->nvenc_session_config.maxEncodeWidth = size.x;
+    this->nvenc_session_config.maxEncodeHeight = size.y;
+    memset(this->nvenc_session_config.maxMEHintCountsPerBlock, 0, sizeof(this->nvenc_session_config.maxMEHintCountsPerBlock));
+    this->nvenc_session_config.tuningInfo = NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY;
+    this->nvenc_session_config.bufferFormat = (NV_ENC_BUFFER_FORMAT) 0;
+    memset(this->nvenc_session_config.reserved, 0, sizeof(this->nvenc_session_config.reserved));
+    memset(this->nvenc_session_config.reserved2, 0, sizeof(this->nvenc_session_config.reserved2));
     
-    if (nvenc_functions.nvEncInitializeEncoder(this->nvenc_session, &session_config) != NV_ENC_SUCCESS)
+    if (nvenc_functions.nvEncInitializeEncoder(this->nvenc_session, &this->nvenc_session_config) != NV_ENC_SUCCESS)
     {
         lava::log()->error("Can't init nvenc session!");
 
@@ -489,7 +712,7 @@ void NvidiaEncoder::destroy_session()
 
 }
 
-bool NvidiaEncoder::create_input_buffer(NvidiaEncoderFrame::Ptr frame, lava::device_ptr device, const glm::uvec2& size)
+bool NvidiaEncoder::create_input_buffer(NvidiaEncoderFrame::Ptr frame, lava::device_ptr device, const glm::uvec2& size, EncoderFormat format)
 {
     VkExternalMemoryImageCreateInfo export_image_info;
     export_image_info.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
@@ -501,7 +724,7 @@ bool NvidiaEncoder::create_input_buffer(NvidiaEncoderFrame::Ptr frame, lava::dev
     image_info.pNext = &export_image_info;
     image_info.flags = 0;
     image_info.imageType = VK_IMAGE_TYPE_2D;
-    image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
+    image_info.format = (VkFormat)format;
     image_info.extent.width = size.x;
     image_info.extent.height = size.y;
     image_info.extent.depth = 1;
@@ -605,12 +828,11 @@ bool NvidiaEncoder::create_input_buffer(NvidiaEncoderFrame::Ptr frame, lava::dev
     image_subresources.mipLevel = 0;
     image_subresources.arrayLayer = 0;
 
-    VkSubresourceLayout image_subresource_layout;
-    vkGetImageSubresourceLayout(device->get(), frame->image, &image_subresources, &image_subresource_layout);
+    vkGetImageSubresourceLayout(device->get(), frame->image, &image_subresources, &frame->image_layout);
 
     CUDA_EXTERNAL_MEMORY_BUFFER_DESC buffer_description;
-    buffer_description.offset = image_subresource_layout.offset;
-    buffer_description.size = image_subresource_layout.size;
+    buffer_description.offset = frame->image_layout.offset;
+    buffer_description.size = frame->image_layout.size;
     buffer_description.flags = 0;
     memset(buffer_description.reserved, 0, sizeof(buffer_description.reserved));
 
@@ -619,16 +841,31 @@ bool NvidiaEncoder::create_input_buffer(NvidiaEncoderFrame::Ptr frame, lava::dev
         return false;
     }
 
+    NV_ENC_BUFFER_FORMAT buffer_format;
+
+    switch (format)
+    {
+    case ENCODER_FORMAT_R8G8B8A8_SRGB:
+        buffer_format = NV_ENC_BUFFER_FORMAT_ABGR;
+        break;
+    case ENCODER_FORMAT_B8G8R8A8_SRGB:
+        buffer_format = NV_ENC_BUFFER_FORMAT_ARGB;
+        break;
+    default:
+        lava::log()->error("Unkown format!");
+        return false;
+    }
+
     NV_ENC_REGISTER_RESOURCE register_info;
     register_info.version = NV_ENC_REGISTER_RESOURCE_VER;
     register_info.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
     register_info.width = size.x;
     register_info.height = size.y;
-    register_info.pitch = image_subresource_layout.rowPitch;
+    register_info.pitch = frame->image_layout.rowPitch;
     register_info.subResourceIndex = 0;
     register_info.resourceToRegister = (void*)frame->cuda_buffer;
     register_info.registeredResource = nullptr;
-    register_info.bufferFormat = NV_ENC_BUFFER_FORMAT_ABGR;
+    register_info.bufferFormat = buffer_format;
     register_info.bufferUsage = NV_ENC_INPUT_IMAGE;
     register_info.pInputFencePoint = nullptr;
     memset(register_info.reserved1, 0, sizeof(register_info.reserved1));
@@ -639,6 +876,7 @@ bool NvidiaEncoder::create_input_buffer(NvidiaEncoderFrame::Ptr frame, lava::dev
         return false;
     }
 
+    frame->image_size = size;
     frame->nvenc_input_buffer = register_info.registeredResource;
 
     return true;
@@ -672,7 +910,7 @@ bool NvidiaEncoder::create_async_event(NvidiaEncoderFrame::Ptr frame)
 {
 #if defined(_WIN32)
     frame->event_handle = CreateEvent(nullptr, true, false, nullptr);
-    frame->async_event = NvidiaEncoderFrame::EventType(this->worker_executor, frame->event_handle);
+    frame->async_event = NvidiaEncoderFrame::EventType(this->worker_pool.get_executor(), frame->event_handle);
 #else
     #error "Not implemented for this platform!"
 #endif
@@ -744,6 +982,21 @@ bool NvidiaEncoder::create_semaphore(NvidiaEncoderFrame::Ptr frame, lava::device
 void NvidiaEncoder::destroy_frame(NvidiaEncoderFrame::Ptr frame)
 {
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 }
 
 bool NvidiaEncoder::check_encode_support(GUID required_guid) const
diff --git a/src/encoder/nvidia_encoder.hpp b/src/encoder/nvidia_encoder.hpp
index 71b25041..796c6e07 100644
--- a/src/encoder/nvidia_encoder.hpp
+++ b/src/encoder/nvidia_encoder.hpp
@@ -15,6 +15,8 @@
 
 #include "encoder.hpp"
 
+#define NVIDIA_ENCODER_FRAMES 10
+
 struct NvidiaEncoderFrame
 {
 public:
@@ -22,6 +24,9 @@ public:
     typedef asio::windows::basic_object_handle<asio::executor> EventType;
 
 public:
+    glm::uvec2 image_size;
+    VkSubresourceLayout image_layout;
+
     VkImage image = VK_NULL_HANDLE;
     VkDeviceMemory device_memory = VK_NULL_HANDLE;
     VkSemaphore semaphore = VK_NULL_HANDLE;
@@ -37,6 +42,11 @@ public:
 
     void* nvenc_input_buffer = nullptr;
     void* nvenc_output_buffer = nullptr;
+    void* nvenc_mapped_buffer = nullptr;
+
+    bool output_parameter = false;
+
+    Encoder::OnEncodeComplete on_encode_complete;
 };
 
 class NvidiaEncoder : public Encoder
@@ -45,9 +55,9 @@ public:
     typedef std::shared_ptr<NvidiaEncoder> Ptr;
 
 public:
-    NvidiaEncoder() = default;
+    NvidiaEncoder();
 
-    bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t input_buffers);
+    bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, EncoderFormat format);
     void destroy();
 
     bool encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function);
@@ -57,28 +67,31 @@ public:
     void set_mode(EncoderMode mode);
     void set_quality(double quality);
     void set_bitrate(double bitrate);
-    void set_key_rate(uint32_t key_rate);
     void set_frame_rate(uint32_t frame_rate);
 
     EncoderMode get_mode() const;
     double get_quality() const;
     double get_bitrate() const;
-    uint32_t get_key_rate() const;
     uint32_t get_frame_rate() const;
 
+    bool is_supported(EncoderSetting setting) const;
+
 private:
     bool aquire_frame(NvidiaEncoderFrame::Ptr& frame);
     void release_frame(NvidiaEncoderFrame::Ptr frame);
     void submit_frame(NvidiaEncoderFrame::Ptr frame, lava::renderer& renderer);
     void read_frame(NvidiaEncoderFrame::Ptr frame);
 
+    bool apply_config();
+    void submit_encode_task(NvidiaEncoderFrame::Ptr frame);
+
     bool create_context(lava::device_ptr device);
     void destroy_context();
 
     bool create_session(const glm::uvec2& size);
     void destroy_session();
 
-    bool create_input_buffer(NvidiaEncoderFrame::Ptr frame, lava::device_ptr device, const glm::uvec2& size);
+    bool create_input_buffer(NvidiaEncoderFrame::Ptr frame, lava::device_ptr device, const glm::uvec2& size, EncoderFormat format);
     bool create_output_buffer(NvidiaEncoderFrame::Ptr frame);
     bool create_async_event(NvidiaEncoderFrame::Ptr frame);
     bool create_semaphore(NvidiaEncoderFrame::Ptr frame, lava::device_ptr device);
@@ -95,17 +108,20 @@ private:
     EncoderMode mode = ENCODER_MODE_CONSTANT_QUALITY;
     double quality = 0.0;
     double bitrate = 5.0;
-    uint32_t key_rate = 90;
     uint32_t frame_rate = 90;
 
+    bool config_change = true;
+    bool parameter_change = true;
+
 private:
-    std::thread worker_thread;
-    asio::io_context worker_executor;
+    asio::thread_pool worker_pool;
 
     CUdevice cuda_device;
     CUcontext cuda_context;
 
     void* nvenc_session = nullptr;
+    NV_ENC_INITIALIZE_PARAMS nvenc_session_config;
+    NV_ENC_CONFIG nvenc_encode_config;
 
     std::mutex frame_mutex;
     std::vector<NvidiaEncoderFrame::Ptr> frame_queue; //NOTE: Protected by frame_mutex
diff --git a/src/encoder/vulkan_encoder.cpp b/src/encoder/vulkan_encoder.cpp
index 888ecadb..578b2a31 100644
--- a/src/encoder/vulkan_encoder.cpp
+++ b/src/encoder/vulkan_encoder.cpp
@@ -2,7 +2,12 @@
 #include <array>
 #include <vk_video/vulkan_video_codecs_common.h>
 
-bool VulkanEncoder::create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t frame_count)
+VulkanEncoder::VulkanEncoder() : worker_pool(1)
+{
+
+}
+
+bool VulkanEncoder::create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, EncoderFormat format)
 {
     //Get the default graphics queue of lava for querey ownership transiations
     this->default_queue = renderer.get_queue();
@@ -144,7 +149,7 @@ bool VulkanEncoder::create(lava::device_ptr device, const lava::renderer& render
         }
 
         //Allocate a command buffer as well as a semaphore and a fence for the frame.
-        if (!this->create_encode_resources(device, this->worker_executor.get_executor(), frame))
+        if (!this->create_encode_resources(device, this->worker_pool.get_executor(), frame))
         {
             return false;
         }
@@ -158,11 +163,6 @@ bool VulkanEncoder::create(lava::device_ptr device, const lava::renderer& render
     this->frame_count = frame_count;
     this->frame_index = 0;
 
-    this->worker_thread = std::thread([this]()
-    {
-        this->worker_executor.run();
-    });
-
     return true;
 }
 
@@ -359,6 +359,11 @@ uint32_t VulkanEncoder::get_frame_rate() const
     return this->setting_frame_rate;
 }
 
+bool VulkanEncoder::is_supported(EncoderSetting setting) const
+{
+    return true;
+}
+
 bool VulkanEncoder::encode_control(lava::renderer& renderer, OnEncodeComplete callback)
 {
     if (!this->control_change)
diff --git a/src/encoder/vulkan_encoder.hpp b/src/encoder/vulkan_encoder.hpp
index a3e4b829..1684571b 100644
--- a/src/encoder/vulkan_encoder.hpp
+++ b/src/encoder/vulkan_encoder.hpp
@@ -12,6 +12,8 @@
 #include "encoder.hpp"
 #include "utility/extern_fence.hpp"
 
+#define VULKAN_ENCODER_FRAMES 10
+
 enum VulkanEncoderFrameType
 {
     VULKAN_ENCODER_FRAME_TYPE_CONTROL,
@@ -75,9 +77,9 @@ public:
     typedef std::shared_ptr<VulkanEncoder> Ptr;
 
 public:
-    VulkanEncoder() = default;
+    VulkanEncoder();
 
-    bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t input_buffers);
+    bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, EncoderFormat format);
     void destroy();
 
     bool encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function);
@@ -96,6 +98,8 @@ public:
     uint32_t get_key_rate() const;
     uint32_t get_frame_rate() const;
 
+    bool is_supported(EncoderSetting setting) const;
+
 private:
     bool encode_control(lava::renderer& renderer, OnEncodeComplete callback);
     bool encode_config(lava::renderer& renderer, OnEncodeComplete callback);
@@ -177,8 +181,7 @@ private:
     uint32_t setting_buffer_size;
 
 private:
-    std::thread worker_thread;
-    asio::io_context worker_executor;
+    asio::thread_pool worker_pool;
 
     VkSampler sampler = nullptr;
     VkCommandPool command_pool = nullptr;
diff --git a/src/headset/emulated_headset.cpp b/src/headset/emulated_headset.cpp
index 61fd3c7e..0df35a1d 100644
--- a/src/headset/emulated_headset.cpp
+++ b/src/headset/emulated_headset.cpp
@@ -5,15 +5,13 @@
 #include <glm/gtx/matrix_operation.hpp>
 
 //DEBUG!!!
+#include <fstream>
 #include "encoder/encoder.hpp"
+Encoder::Ptr encoder = nullptr;
+std::fstream file;
 
 bool EmulatedHeadset::on_create()
 {
-    //DEBUG!!
-    Encoder::Ptr encoder = make_encoder(ENCODER_TYPE_NVIDIA);
-    encoder->create(this->get_application()->get_device(), this->get_application()->get_renderer(), glm::uvec2(1920, 1080), 4);
-
-
     lava::camera& camera = this->get_application()->get_camera();
     camera.movement_speed = 1.0f;
     
@@ -32,12 +30,21 @@ bool EmulatedHeadset::on_create()
         return false;
     }
 
+    //DEBUG!!
+    encoder = make_encoder(ENCODER_TYPE_NVIDIA);
+    encoder->create(this->get_application()->get_device(), this->get_application()->get_renderer(), this->resolution, (EncoderFormat)this->get_format());
+
+    file.open("video.h264", std::ios::out | std::ios::binary);
+
     return true;
 }
 
 void EmulatedHeadset::on_destroy()
 {
     this->destroy_framebuffer();
+
+    //DEBUG:
+    file.close();
 }
 
 bool EmulatedHeadset::on_interface()
@@ -82,6 +89,15 @@ void EmulatedHeadset::submit_frame(VkCommandBuffer command_buffer, VkImageLayout
     lava::image::ptr window_image = this->get_application()->get_target()->get_backbuffer(this->get_application()->get_frame_index());
     lava::image::ptr frame_image = this->get_framebuffer(frame_id);
 
+    //DEBUG!!
+    if (frame_id == 0)
+    {
+        encoder->encode(command_buffer, this->get_application()->get_renderer(), frame_image, frame_layout, [this](const std::span<uint8_t>& content, bool is_config)
+        {
+            file.write((const char*)content.data(), content.size());
+        });
+    }
+    
     std::vector<VkImageMemoryBarrier> image_barriers;
 
     VkImageMemoryBarrier window_barrier = lava::image_memory_barrier(window_image->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
diff --git a/src/headset/remote_headset.cpp b/src/headset/remote_headset.cpp
index 59509122..f7614bd5 100644
--- a/src/headset/remote_headset.cpp
+++ b/src/headset/remote_headset.cpp
@@ -444,9 +444,8 @@ bool RemoteHeadset::create_encoders()
 
         lava::device_ptr device = this->get_application()->get_device();
         lava::renderer& renderer = this->get_application()->get_renderer();
-        uint32_t frame_count = this->get_application()->get_frame_count();
-
-        if (!encoder->create(device, renderer, this->resolution, frame_count))
+        
+        if (!encoder->create(device, renderer, this->resolution, (EncoderFormat)this->get_format()))
         {
             return false;
         }
-- 
GitLab