diff --git a/src/encoder/encoder.hpp b/src/encoder/encoder.hpp
index a97e2b616e3b44681f8645b2e6773a3fa2d1460e..ac89a55f9299c2ea74f05c2a76b2946a46485f95 100644
--- a/src/encoder/encoder.hpp
+++ b/src/encoder/encoder.hpp
@@ -90,16 +90,17 @@ public:
     virtual bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, EncoderFormat format) = 0;
     virtual void destroy() = 0;
 
-    // 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 EncoderResult encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function) = 0;
+    virtual EncoderResult encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, uint32_t timestamp, OnEncodeComplete function) = 0;
+
+    // Can be used to invalidate the reference frame that was encodided using the specified timestamp.
+    virtual bool invalidate(uint32_t timestamp) = 0;
 
     // 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;
 
-    // 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
@@ -107,7 +108,6 @@ public:
     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;
diff --git a/src/encoder/nvidia_encoder.cpp b/src/encoder/nvidia_encoder.cpp
index 0823eaf8c958f3ea4ed00da107162e7d1b9217d8..e9f8b5c54ce696e8ffd0742261336a4b5560bd96 100644
--- a/src/encoder/nvidia_encoder.cpp
+++ b/src/encoder/nvidia_encoder.cpp
@@ -88,7 +88,7 @@ void NvidiaEncoder::destroy()
     this->destroy_context();
 }
 
-EncoderResult NvidiaEncoder::encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function)
+EncoderResult NvidiaEncoder::encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, uint32_t timestamp, OnEncodeComplete function)
 {
     NvidiaEncoderFrame::Ptr frame;
 
@@ -97,6 +97,7 @@ EncoderResult NvidiaEncoder::encode(VkCommandBuffer command_buffer, lava::render
         return ENCODER_RESULT_FRAME_DROPPED;
     }
 
+    frame->timestamp = timestamp;
     frame->output_parameter = this->parameter_change;
     frame->on_encode_complete = std::move(function);
 
@@ -203,6 +204,18 @@ EncoderResult NvidiaEncoder::encode(VkCommandBuffer command_buffer, lava::render
     return ENCODER_RESULT_SUCCESS;
 }
 
+bool NvidiaEncoder::invalidate(uint32_t timestamp)
+{
+    if (nvenc_functions.nvEncInvalidateRefFrames(this->nvenc_session, timestamp) != NV_ENC_SUCCESS)
+    {
+        lava::log()->error("Nvidia Encoder: Can't invalidate frame!");
+        
+        return false;
+    }
+
+    return true;
+}
+
 void NvidiaEncoder::set_on_encode_error(OnEncodeError function)
 {
     this->on_encode_error = std::move(function);
@@ -449,6 +462,9 @@ bool NvidiaEncoder::apply_config()
     return true;
 }
 
+uint32_t temp_index = 0;
+uint32_t write_index = 0;
+
 void NvidiaEncoder::submit_encode_task(NvidiaEncoderFrame::Ptr frame)
 {
     if (cuCtxPushCurrent(this->cuda_context) != CUDA_SUCCESS)
@@ -512,6 +528,22 @@ void NvidiaEncoder::submit_encode_task(NvidiaEncoderFrame::Ptr frame)
     codec_extention_parameters.mvcPicParams = motion_vector_parameters;
     memset(codec_extention_parameters.reserved1, 0, sizeof(codec_extention_parameters.reserved1));
     
+    uint32_t mark = 0;
+    uint32_t mark_index = 0;
+    uint32_t read_mask = 0;
+
+    if (temp_index > 0)
+    {
+        read_mask = 0x3FF & ~(1 << write_index);
+    }
+
+    if (temp_index % (90) == 0)
+    {
+        mark = 1;
+        mark_index = write_index;
+        write_index = (write_index + 1) % 10;
+    }
+
     NV_ENC_PIC_PARAMS_H264 codec_parameters;
     codec_parameters.displayPOCSyntax = 0;
     codec_parameters.reserved3 = 0;
@@ -520,7 +552,7 @@ void NvidiaEncoder::submit_encode_task(NvidiaEncoderFrame::Ptr frame)
     codec_parameters.forceIntraRefreshWithFrameCnt = 0;
     codec_parameters.constrainedFrame = 1;
     codec_parameters.sliceModeDataUpdate = 1;
-    codec_parameters.ltrMarkFrame = 0;
+    codec_parameters.ltrMarkFrame = mark;
     codec_parameters.ltrUseFrames = 0;
     codec_parameters.reservedBitFields = 0;
     codec_parameters.sliceTypeData = 0;
@@ -529,8 +561,8 @@ void NvidiaEncoder::submit_encode_task(NvidiaEncoderFrame::Ptr frame)
     codec_parameters.seiPayloadArray = nullptr;
     codec_parameters.sliceMode = 3;
     codec_parameters.sliceModeData = 10;
-    codec_parameters.ltrMarkFrameIdx = 0;
-    codec_parameters.ltrUseFrameBitmap = 0;
+    codec_parameters.ltrMarkFrameIdx = mark_index;
+    codec_parameters.ltrUseFrameBitmap = read_mask;
     codec_parameters.ltrUsageMode = 0;
     codec_parameters.forceIntraSliceCount = 0;
     codec_parameters.forceIntraSliceIdx = 0;
@@ -545,7 +577,7 @@ void NvidiaEncoder::submit_encode_task(NvidiaEncoderFrame::Ptr frame)
     encode_parameters.inputPitch = frame->image_layout.rowPitch;
     encode_parameters.encodePicFlags = 0;
     encode_parameters.frameIdx = 0;
-    encode_parameters.inputTimeStamp = 0;
+    encode_parameters.inputTimeStamp = frame->timestamp;
     encode_parameters.inputDuration = 0;
     encode_parameters.inputBuffer = frame->nvenc_mapped_buffer;
     encode_parameters.outputBitstream = frame->nvenc_output_buffer;
@@ -573,6 +605,8 @@ void NvidiaEncoder::submit_encode_task(NvidiaEncoderFrame::Ptr frame)
 
         return;
     }
+
+    temp_index++;
 }
 
 bool NvidiaEncoder::create_context(lava::device_ptr device)
@@ -717,6 +751,8 @@ bool NvidiaEncoder::create_session(const glm::uvec2& size)
     this->nvenc_encode_config.encodeCodecConfig.h264Config.enableIntraRefresh = 1; //NOTE: Can create problems when replayed in VLC
     this->nvenc_encode_config.encodeCodecConfig.h264Config.intraRefreshPeriod = this->frame_rate * 2;
     this->nvenc_encode_config.encodeCodecConfig.h264Config.intraRefreshCnt = 10;
+    this->nvenc_encode_config.encodeCodecConfig.h264Config.enableLTR = 1;
+    this->nvenc_encode_config.encodeCodecConfig.h264Config.ltrNumFrames = 10;
     this->nvenc_encode_config.rcParams.enableAQ = 1;
     this->nvenc_encode_config.rcParams.aqStrength = 0;
 
diff --git a/src/encoder/nvidia_encoder.hpp b/src/encoder/nvidia_encoder.hpp
index 4a364f1e940ec323903ab2d1ae0a5f53fb860048..ad2f9a3199c0ccadf38e3f7deb0ab60ac6f73a23 100644
--- a/src/encoder/nvidia_encoder.hpp
+++ b/src/encoder/nvidia_encoder.hpp
@@ -39,6 +39,7 @@ public:
 
     bool output_parameter = false;
 
+    uint32_t timestamp = 0;
     Encoder::OnEncodeComplete on_encode_complete;
 };
 
@@ -53,7 +54,8 @@ public:
     bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, EncoderFormat format) override;
     void destroy() override;
 
-    EncoderResult encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function) override;
+    EncoderResult encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, uint32_t timestamp, OnEncodeComplete function) override;
+    bool invalidate(uint32_t timestamp) override;
 
     void set_on_encode_error(OnEncodeError function) override;
 
diff --git a/src/encoder/vulkan_encoder.cpp b/src/encoder/vulkan_encoder.cpp
index 48a17447effad710a54f7f0e9856513ee2a5a0d3..ddbefcce5365c80df1d7218387d96af62d4502ce 100644
--- a/src/encoder/vulkan_encoder.cpp
+++ b/src/encoder/vulkan_encoder.cpp
@@ -182,7 +182,7 @@ void VulkanEncoder::destroy()
     this->destroy_session(device);
 }
 
-EncoderResult VulkanEncoder::encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete callback)
+EncoderResult VulkanEncoder::encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, uint32_t timestamp, OnEncodeComplete callback)
 {
     if (!this->encode_control(renderer, callback))
     {
@@ -204,6 +204,7 @@ EncoderResult VulkanEncoder::encode(VkCommandBuffer command_buffer, lava::render
     }
 
     frame->frame_index = this->frame_index;
+    frame->timestamp = timestamp;
     frame->type = VULKAN_ENCODER_FRAME_TYPE_FRAME;
     frame->callback = std::move(callback);
 
@@ -298,6 +299,24 @@ EncoderResult VulkanEncoder::encode(VkCommandBuffer command_buffer, lava::render
     return ENCODER_RESULT_SUCCESS;
 }
 
+bool VulkanEncoder::invalidate(uint32_t timestamp)
+{
+    for (VulkanEncoderFrame::Ptr& slot : this->frame_slots)
+    {
+        if (slot != nullptr)
+        {
+            if (slot->timestamp == timestamp)
+            {
+                slot = nullptr;
+
+                break;
+            }
+        }
+    }
+
+    return true;
+}
+
 void VulkanEncoder::set_on_encode_error(OnEncodeError function)
 {
     this->on_encode_error = std::move(function);
@@ -2171,6 +2190,13 @@ void VulkanEncoder::encode_pass_frame_setup(VkCommandBuffer command_buffer, Vulk
 
 void VulkanEncoder::encode_pass_frame_command(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame, const std::vector<VulkanEncoderFrame::Ptr>& reference_slots)
 {
+    bool key_frame = this->is_key_frame();
+
+    if (reference_slots.empty())
+    {
+        key_frame = true;
+    }
+
     VkOffset2D encode_offset;
     encode_offset.x = 0;
     encode_offset.y = 0;
diff --git a/src/encoder/vulkan_encoder.hpp b/src/encoder/vulkan_encoder.hpp
index cea0f938b75bbcab1369a64da352ca22a50444ab..c935d876ab92795d7f5c3a8fd28d472231b7d5fa 100644
--- a/src/encoder/vulkan_encoder.hpp
+++ b/src/encoder/vulkan_encoder.hpp
@@ -65,6 +65,7 @@ public:
     ExternFence::Ptr encode_fence;
 
     uint32_t frame_index = 0;
+    uint32_t timestamp = 0;
     VulkanEncoderFrameType type;
     Encoder::OnEncodeComplete callback;
 };
@@ -80,8 +81,9 @@ public:
     bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, EncoderFormat format) override;
     void destroy() override;
 
-    EncoderResult encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function) override;
-
+    EncoderResult encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, uint32_t timestamp, OnEncodeComplete function) override;
+    bool invalidate(uint32_t timestamp) override;
+    
     void set_on_encode_error(OnEncodeError function) override;
 
     void set_mode(EncoderMode mode) override;
diff --git a/src/headset/remote_headset.cpp b/src/headset/remote_headset.cpp
index f7e4b94c158ac5c15a00fc8dcfbb22ec3ca93ec1..ae4d6f8287e9befb6b50251cd7c3ccac47b3574b 100644
--- a/src/headset/remote_headset.cpp
+++ b/src/headset/remote_headset.cpp
@@ -120,6 +120,7 @@ bool RemoteHeadset::on_interface()
 
     ImGui::Separator();
 
+    std::unique_lock<std::mutex> encoder_lock(this->encoder_mutex);
     Encoder::Ptr encoder = this->encoders.front();
 
     if (encoder->is_supported(ENCODER_SETTING_MODE_CONSTANT_BITRATE) && encoder->is_supported(ENCODER_SETTING_MODE_CONSTANT_QUALITY))
@@ -195,6 +196,7 @@ bool RemoteHeadset::on_interface()
 
         return false;
     }
+    encoder_lock.unlock();
 
     if (ImGui::SliderInt("Send-Queue Limit (KBytes)", (int32_t*)&this->send_queue_size_limit, 1, 1000))
     {
@@ -211,7 +213,7 @@ bool RemoteHeadset::on_interface()
     std::vector<std::string> graph_names;
     std::vector<const char*> graph_name_pointers;
 
-    std::unique_lock<std::mutex> lock(this->transport_mutex);
+    std::unique_lock<std::mutex> transport_lock(this->transport_mutex);
     for (const Statistic::Ptr& statistic : this->statistic_list)
     {
         if (!statistic->is_log_only())
@@ -219,7 +221,7 @@ bool RemoteHeadset::on_interface()
             graph_names.push_back(statistic->get_display_name());
         }
     }
-    lock.unlock();
+    transport_lock.unlock();
 
     for (const std::string& graph_name : graph_names)
     {
@@ -230,7 +232,7 @@ bool RemoteHeadset::on_interface()
     
     ImGui::Separator();
 
-    lock.lock();
+    transport_lock.lock();
     for (Statistic::Ptr statistic : this->statistic_list)
     {
         if (!statistic->is_log_only())
@@ -238,7 +240,7 @@ bool RemoteHeadset::on_interface()
             statistic->interface(128);
         }
     }
-    lock.unlock();
+    transport_lock.unlock();
 
     return true;
 }
@@ -285,13 +287,16 @@ void RemoteHeadset::submit_frame(VkCommandBuffer command_buffer, VkImageLayout f
     lava::renderer& renderer = this->get_application()->get_renderer();
     lava::image::ptr framebuffer = this->framebuffers[frame_id];
 
-    PassTimer::Ptr pass_timer = this->get_application()->get_pass_timer();
-    Encoder::Ptr encoder = this->encoders[frame_id];    
+    PassTimer::Ptr pass_timer = this->get_application()->get_pass_timer();    
     uint32_t frame_number = this->frame_number;
     uint32_t transform_id = this->transform_id;
 
     pass_timer->begin_pass(command_buffer, "encode_setup_" + std::to_string(frame_id));
-    EncoderResult result = encoder->encode(command_buffer, renderer, framebuffer, frame_layout, [this, frame_number, frame_id, transform_id](const std::span<uint8_t>& content, bool is_config)
+    std::unique_lock<std::mutex> lock(this->encoder_mutex);
+    Encoder::Ptr encoder = this->encoders[frame_id];
+    uint32_t last_invalidate = this->encoder_last_invalidate[frame_id];
+
+    EncoderResult result = encoder->encode(command_buffer, renderer, framebuffer, frame_layout, frame_number, [this, frame_number, frame_id, transform_id, last_invalidate](const std::span<uint8_t>& content, bool is_config)
     {
         if (is_config)
         {
@@ -300,7 +305,7 @@ void RemoteHeadset::submit_frame(VkCommandBuffer command_buffer, VkImageLayout f
 
         else
         {
-            this->transport->send_frame_nal(frame_number, frame_id, transform_id, content);
+            this->transport->send_frame_nal(frame_number, frame_id, transform_id, last_invalidate, content);
         }
 
         if (!this->encoder_files.empty())
@@ -308,6 +313,7 @@ void RemoteHeadset::submit_frame(VkCommandBuffer command_buffer, VkImageLayout f
             this->encoder_files[frame_id].write((const char*)content.data(), content.size());
         }
     });
+    lock.unlock();
     pass_timer->end_pass(command_buffer);
 
     if (result == ENCODER_RESULT_ERROR)
@@ -408,6 +414,10 @@ bool RemoteHeadset::create_transport()
     {
         this->on_controller_thumbstick(controller, thumbstick);
     });
+    this->transport->set_on_frame_invalidate([this](FrameNumber frame_number, FrameId frame_id)
+    {
+        this->on_frame_invalidate(frame_number, frame_id);
+    });
     this->transport->set_on_performance_sample([this](std::optional<FrameNumber> frame_number, std::optional<FrameId> frame_id, std::optional<TransformId> transform_id, Unit unit, bool log_only, double sample, const std::string& name)
     {
         this->on_performance_sample(frame_number, frame_id, transform_id, unit, log_only, sample, name);
@@ -477,6 +487,7 @@ void RemoteHeadset::destroy_framebuffers()
 bool RemoteHeadset::create_encoders()
 {
     this->encoders.resize(this->frame_id_count, nullptr);
+    this->encoder_last_invalidate.resize(this->frame_id_count, 0);
 
     for (uint32_t index = 0; index < this->encoders.size(); index++)
     {
@@ -711,6 +722,18 @@ void RemoteHeadset::on_controller_thumbstick(Controller controller, const glm::v
     lock.unlock();
 }
 
+void RemoteHeadset::on_frame_invalidate(FrameNumber frame_number, FrameId frame_id)
+{
+    std::unique_lock<std::mutex> lock(this->encoder_mutex);
+    Encoder::Ptr encoder = this->encoders[frame_id];
+    encoder->invalidate(frame_number);
+
+    this->encoder_last_invalidate[frame_id] = frame_number;
+    lock.unlock();
+
+    lava::log()->debug("Invalidate Frame Number: {} Frame Id: {}", frame_number, frame_id);
+}
+
 void RemoteHeadset::on_performance_sample(std::optional<FrameNumber> frame_number, std::optional<FrameId> frame_id, std::optional<TransformId> transform_id, Unit unit, bool log_only, double sample, const std::string& name)
 {
     std::unique_lock<std::mutex> lock(this->transport_mutex);
diff --git a/src/headset/remote_headset.hpp b/src/headset/remote_headset.hpp
index 02d97c42b110b0ee58fde8040cadbd19e96ced23..f52aa38001de36985da92fabe88e094555cd464d 100644
--- a/src/headset/remote_headset.hpp
+++ b/src/headset/remote_headset.hpp
@@ -68,6 +68,7 @@ private:
     void on_controller_event(Controller controller, ControllerState controller_state);
     void on_controller_button(Controller controller, Button button, ButtonState button_state);
     void on_controller_thumbstick(Controller controller, const glm::vec2& thumbstick);
+    void on_frame_invalidate(FrameNumber frame_number, FrameId frame_id);
     void on_performance_sample(std::optional<FrameNumber> frame_number, std::optional<FrameId> frame_id, std::optional<TransformId> transform_id, Unit unit, bool log_only, double sample, const std::string& name);
     void on_transport_error();
     void on_encode_error();
@@ -111,7 +112,9 @@ private:
     lava::image::ptr framebuffer_array;
     std::vector<lava::image::ptr> framebuffers;
 
-    std::vector<Encoder::Ptr> encoders;
+    std::mutex encoder_mutex;
+    std::vector<Encoder::Ptr> encoders;            //NOTE: Protected by encoder_mutex
+    std::vector<uint32_t> encoder_last_invalidate; //NOTE: Protected by encoder_mutex
     std::vector<std::fstream> encoder_files;
     uint32_t encoder_mode = ENCODER_MODE_CONSTANT_QUALITY;
     uint32_t encoder_input_rate = 90;
diff --git a/src/transport/transport.hpp b/src/transport/transport.hpp
index eedfcb859ec198cf77306934588bbcd7b108ef3e..7f93ad6644fe47d72521e7ecd3be06e9f4ce8e61 100644
--- a/src/transport/transport.hpp
+++ b/src/transport/transport.hpp
@@ -88,6 +88,11 @@ public:
     //  thumbstick: Position of the thumbstick on the specified controller
     typedef std::function<void(Controller controller, const glm::vec2& thumbstick)> OnControllerThumbstick;
 
+    // OnFrameInvalidate: Used to indicate that a frame was not correctly received.
+    //  frame_number: The the number of the frame. The frame number of the first rendered frame would be zero
+    //      frame_id: The frame id to which the information in this packet belong (e.g. left or right eye)
+    typedef std::function<void(FrameNumber frame_number, FrameId frame_id)> OnFrameInvalidate;
+
     // OnPerformanceSample: Used to transmit performance related samples.
     //  frame_number: Identifies the frame to which the information in this packet belongs
     //      frame_id: Identifies the frame id (e.g. left or right eye) to which the information in this packet belongs
@@ -133,11 +138,12 @@ public:
     // send_frame_nal: Use to send encoded images
     //                 This could be for example an h264 encoded image
     //                 This function should be threadsafe and callable by main and worker thread
-    //  frame_number: The the number of the frame. The frame number of the first rendered frame would be zero
-    //      frame_id: The frame id to which the information in this packet belong (e.g. left or right eye)
-    //  transform_id: The id of the head transformation that was used to render the image in this packet
-    //       content: Encoded image
-    virtual bool send_frame_nal(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, const std::span<const uint8_t>& content) = 0;
+    //     frame_number: The the number of the frame. The frame number of the first rendered frame would be zero
+    //         frame_id: The frame id to which the information in this packet belong (e.g. left or right eye)
+    //     transform_id: The id of the head transformation that was used to render the image in this packet
+    //  last_invalidate: The frame number of the last frame that was invalidated
+    //          content: Encoded image
+    virtual bool send_frame_nal(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, FrameNumber last_invalidate, const std::span<const uint8_t>& content) = 0;
 
     // send_frame_metadata: Used to send additional information that could be needed by the post processing step that was selected in the setup-complete packet
     //                      This function should be threadsafe and callable by main and worker thread
@@ -175,6 +181,7 @@ public:
     virtual void set_on_controller_event(OnControllerEvent function) = 0;
     virtual void set_on_controller_button(OnControllerButton function) = 0;
     virtual void set_on_controller_thumbstick(OnControllerThumbstick function) = 0;
+    virtual void set_on_frame_invalidate(OnFrameInvalidate function) = 0;
     virtual void set_on_performance_sample(OnPerformanceSample function) = 0;
     virtual void set_on_transport_error(OnTransportError function) = 0;
 
diff --git a/src/transport/udp_packet.hpp b/src/transport/udp_packet.hpp
index ab19362951827620eb646bb9897709cece83cf62..45be97073d8373f6b30de8dde9bc32bcc74a6b23 100644
--- a/src/transport/udp_packet.hpp
+++ b/src/transport/udp_packet.hpp
@@ -27,25 +27,26 @@ enum UDPPacketId : uint32_t
     UDP_PACKET_ID_CONTROLLER_THUMBSTICK = 0x08,
     UDP_PACKET_ID_FRAME_CONFIG_NAL      = 0x09,
     UDP_PACKET_ID_FRAME_NAL             = 0x0A,
-    UDP_PACKET_ID_FRAME_METADATA        = 0x0B,
-    UDP_PACKET_ID_PERFORMANCE_SAMPLE    = 0x0C,
-    UDP_PACKET_ID_OVERLAY_CONFIG        = 0x0D,
-    UDP_PACKET_ID_OVERLAY_TEXT          = 0x0E, 
-    UDP_PACKET_ID_OVERLAY_GRAPH         = 0x0F,
+    UDP_PACKET_ID_FRAME_INVALIDATE      = 0x0B,
+    UDP_PACKET_ID_FRAME_METADATA        = 0x0C,
+    UDP_PACKET_ID_PERFORMANCE_SAMPLE    = 0x0D,
+    UDP_PACKET_ID_OVERLAY_CONFIG        = 0x0E,
+    UDP_PACKET_ID_OVERLAY_TEXT          = 0x0F, 
+    UDP_PACKET_ID_OVERLAY_GRAPH         = 0x10,
     UDP_PACKET_ID_MAX_COUNT
 };
 
 enum UDPStereoStrategyId : uint32_t
 {
-    UDP_STEREO_STRATEGY_ID_DEBUG = 0x00,
+    UDP_STEREO_STRATEGY_ID_DEBUG          = 0x00,
     UDP_STEREO_STRATEGY_ID_NATIVE_STEREO  = 0x01,   
     UDP_STEREO_STRATEGY_ID_MAX_COUNT
 };
 
 enum UDPControllerId : uint32_t
 {
-    UDP_CONTROLLER_ID_LEFT  = 0x00,
-    UDP_CONTROLLER_ID_RIGHT = 0x01,
+    UDP_CONTROLLER_ID_LEFT      = 0x00,
+    UDP_CONTROLLER_ID_RIGHT     = 0x01,
     UDP_CONTROLLER_ID_MAX_COUNT
 };
 
@@ -57,11 +58,11 @@ enum UDPControllerState : uint32_t
 
 enum UDPButtonId : uint32_t
 {
-    UDP_BUTTON_ID_A       = 0x00,
-    UDP_BUTTON_ID_B       = 0x01,
-    UDP_BUTTON_ID_X       = 0x02,
-    UDP_BUTTON_ID_Y       = 0x03,
-    UDP_BUTTON_ID_TRIGGER = 0x04,
+    UDP_BUTTON_ID_A         = 0x00,
+    UDP_BUTTON_ID_B         = 0x01,
+    UDP_BUTTON_ID_X         = 0x02,
+    UDP_BUTTON_ID_Y         = 0x03,
+    UDP_BUTTON_ID_TRIGGER   = 0x04,
     UDP_BUTTON_ID_MAX_COUNT
 };
 
@@ -225,6 +226,7 @@ struct UDPPacketFrameNal
     UDPFrameNumber frame_number; //In case of fragmentation, frame number and frame id are used together as identifier
     UDPFrameId frame_id;
     UDPTransformId transform_id;
+    UDPFrameNumber last_invalidate;
 
     uint32_t payload_offset;
     uint32_t payload_size;
@@ -232,6 +234,15 @@ struct UDPPacketFrameNal
     //Followed by: Nal
 };
 
+struct UDPPacketFrameInvalidate
+{
+    UDPPacketId id = UDP_PACKET_ID_FRAME_INVALIDATE;
+    uint32_t size = 0;
+
+    UDPFrameNumber frame_number;
+    UDPFrameId frame_id;
+};
+
 //NOTE: Assume frame metadata of a particular frame number is always send before any frame nals of the same frame number
 //NOTE: The packet needs to be fragmented if the payload is too big
 struct UDPPacketFrameMetadata
diff --git a/src/transport/udp_transport.cpp b/src/transport/udp_transport.cpp
index 311101a55ee025b2f0960709ac85fe491ab34938..c73891c5467ce9de2d35108fbe74f7c0e7c12fda 100644
--- a/src/transport/udp_transport.cpp
+++ b/src/transport/udp_transport.cpp
@@ -189,7 +189,7 @@ bool UDPTransport::send_frame_config_nal(FrameId frame_id, const std::span<const
     return true;
 }
 
-bool UDPTransport::send_frame_nal(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, const std::span<const uint8_t>& content)
+bool UDPTransport::send_frame_nal(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, FrameNumber last_invalidate, const std::span<const uint8_t>& content)
 {
     if (this->get_state() != TRANSPORT_STATE_CONNECTED)
     {
@@ -212,6 +212,7 @@ bool UDPTransport::send_frame_nal(FrameNumber frame_number, FrameId frame_id, Tr
         packet->frame_number = frame_number;
         packet->frame_id = frame_id;
         packet->transform_id = transform_id;
+        packet->last_invalidate = last_invalidate;
         packet->payload_offset = offset;
         packet->payload_size = content.size();
 
@@ -374,6 +375,11 @@ void UDPTransport::set_on_controller_thumbstick(OnControllerThumbstick function)
     this->on_controller_thumbstick = std::move(function);
 }
 
+void UDPTransport::set_on_frame_invalidate(OnFrameInvalidate function)
+{
+    this->on_frame_invalidate = std::move(function);
+}
+
 void UDPTransport::set_on_performance_sample(OnPerformanceSample function)
 {
     this->on_performance_sample = std::move(function);
@@ -602,6 +608,7 @@ void UDPTransport::receive_datagram()
         case UDP_PACKET_ID_CONTROLLER_EVENT:    
         case UDP_PACKET_ID_CONTROLLER_BUTTON:     
         case UDP_PACKET_ID_CONTROLLER_THUMBSTICK:
+        case UDP_PACKET_ID_FRAME_INVALIDATE:
         case UDP_PACKET_ID_PERFORMANCE_SAMPLE:
             this->receive_queue.push_back(datagram);
             break;
@@ -757,6 +764,9 @@ void UDPTransport::process_receive_queue()
             case UDP_PACKET_ID_CONTROLLER_THUMBSTICK:
                 this->parse_controller_thumbstick(datagram);
                 break;
+            case UDP_PACKET_ID_FRAME_INVALIDATE:
+                this->parse_frame_invalidate(datagram);
+                break;
             case UDP_PACKET_ID_PERFORMANCE_SAMPLE:
                 this->parse_performance_sample(datagram);
                 break;
@@ -955,6 +965,22 @@ void UDPTransport::parse_controller_thumbstick(const Datagram& datagram)
     this->on_controller_thumbstick(controller, packet->thumbstick);
 }
 
+void UDPTransport::parse_frame_invalidate(const Datagram& datagram)
+{
+    UDPPacketFrameInvalidate* packet = (UDPPacketFrameInvalidate*)datagram.buffer;
+
+    if (packet->id != UDP_PACKET_ID_FRAME_INVALIDATE)
+    {
+        lava::log()->error("UDP-Transport: Wrong packet id for frame invalidate packet!");
+        this->on_transport_error();
+        this->set_state(TRANSPORT_STATE_ERROR);
+
+        return;
+    }
+
+    this->on_frame_invalidate(packet->frame_number, packet->frame_id);
+}
+
 void UDPTransport::parse_performance_sample(const Datagram& datagram)
 {
     UDPPacketPerformanceSample* packet = (UDPPacketPerformanceSample*)datagram.buffer;
diff --git a/src/transport/udp_transport.hpp b/src/transport/udp_transport.hpp
index 4a80198c49d7c6cdf3b49307be3a4fd1797ad75a..3bd1dc2c4fc320956863b16638d6f35dc0ee953b 100644
--- a/src/transport/udp_transport.hpp
+++ b/src/transport/udp_transport.hpp
@@ -26,7 +26,7 @@ public:
     bool send_setup_complete(RemoteStrategy remote_strategy, uint32_t max_frame_ids, float near_plane, float far_plane) override;
     bool send_stage_transform(const glm::mat4& stage_transform) override;
     bool send_frame_config_nal(FrameId frame_id, const std::span<const uint8_t>& content) override;
-    bool send_frame_nal(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, const std::span<const uint8_t>& content) override;
+    bool send_frame_nal(FrameNumber frame_number, FrameId frame_id, TransformId transform_id, FrameNumber last_invalidate, const std::span<const uint8_t>& content) override;
     bool send_frame_metadata(FrameNumber frame_number, const std::span<const uint8_t>& content) override;
     bool send_overlay_config(bool overlay_enable) override;
     bool send_overlay_text(FrameNumber frame_number, uint32_t overlay_index, const std::string& label, const std::string& text) override;
@@ -39,6 +39,7 @@ public:
     void set_on_controller_event(OnControllerEvent function) override;
     void set_on_controller_button(OnControllerButton function) override;
     void set_on_controller_thumbstick(OnControllerThumbstick function) override;
+    void set_on_frame_invalidate(OnFrameInvalidate function) override;
     void set_on_performance_sample(OnPerformanceSample function) override;
     void set_on_transport_error(OnTransportError function) override;
 
@@ -68,6 +69,7 @@ private:
     void parse_controller_event(const Datagram& datagram);
     void parse_controller_button(const Datagram& datagram);
     void parse_controller_thumbstick(const Datagram& datagram);
+    void parse_frame_invalidate(const Datagram& datagram);
     void parse_performance_sample(const Datagram& datagram);
 
 private:
@@ -104,5 +106,6 @@ private:
     OnControllerButton on_controller_button;
     OnControllerThumbstick on_controller_thumbstick;
     OnPerformanceSample on_performance_sample;
+    OnFrameInvalidate on_frame_invalidate;
     OnTransportError on_transport_error;
 };
\ No newline at end of file