Capture final frame color buffer to file

More
2 months 1 day ago - 2 months 1 day ago #1 by KianA
Hi @Crosire,

I am working on a project which involves capturing raw uncompressed Game frames. And at a later time, pass them through FFmpeg for encoding.
Is there a way I can capture such raw uncompressed frames (final frame color buffer) using the reshade application irrespective of the graphics API used by the Game ?
It is fine if it works for just dx11 games.
Also, it would be helpful if you can tell any libraries that would let me dump the frames into a file.

P.S.: I don't have access to 4K Monitor but I want to capture 4K frame color buffer rendered by NVIDIA GPU.

Thanks in advance!


 
Last edit: 2 months 1 day ago by KianA.

Please Log in or Create an account to join the conversation.

More
1 month 3 weeks ago - 1 month 3 weeks ago #2 by crosire
Replied by crosire on topic Capture final frame color buffer to file
It's possible in ReShade 5 (would need to build current ReShade source from GitHub, see also github.com/crosire/reshade/tree/main/include) with an add-on.

Will see if I can add an example (to github.com/crosire/reshade/tree/main/examples) that shows how to write an add-on that captures the frame data and encodes it into a video with ffmpeg.

For now, here is a quick example (compile this to a DLL, rename the file extension from ".dll" to ".addon" and put it in the same directory as a ReShade 5 build for it to load) of how to get the raw data of the current frame on the CPU at least (not particularly fast code, but shows the general flow):
#include <cassert>
#include <reshade.hpp>

struct user_data
{
    static const uint8_t GUID[16] = { 0x0D, 0x75, 0x25, 0xF9, 0xC4, 0xE1, 0x42, 0x6E, 0xBC, 0x99, 0x15, 0xBB, 0xD5, 0xFD, 0x51, 0xF2 };

    reshade::api::resource host_resource = { 0 };
};

static void on_init(reshade::api::swapchain *swapchain)
{
    user_data &data = swapchain->create_user_data<user_data>;(user_data::GUID);

    reshade::api::device *const device = swapchain->get_device();

    // Get description of the back buffer resources
    const reshade::api::resource_desc desc = device->get_resource_desc(swapchain->get_current_back_buffer());

    // Create a CPU-accessible texture with matching dimensions
    if (!device->create_resource(
            reshade::api::resource_desc(
                desc.texture.width,
                desc.texture.height,
                1,
                1,
                desc.texture.format,
                1,
                reshade::api::memory_heap::gpu_to_cpu,
                reshade::api::resource_usage::copy_dest),
            nullptr,
            reshade::api::resource_usage::cpu_access,
            &data.host_resource))
    {
        reshade::log_message(1, "Failed to create host resource");
        return;
    }
}
static void on_destroy(reshade::api::swapchain *swapchain)
{
    user_data &data = swapchain->get_user_data<user_data>;(user_data::GUID);

    if (data.host_resource != 0)
    {
        swapchain->get_device()->destroy_resource(data.host_resource);
    }

    swapchain->destroy_user_data<user_data>;(user_data::GUID);
}

static void on_present(reshade::api::command_queue *queue, reshade::api::swapchain *swapchain)
{
    user_data &data = swapchain->get_user_data<user_data>;(user_data::GUID);

    reshade::api::device *const device = swapchain->get_device();

    reshade::api::command_list *cmd_list = queue->get_immediate_command_list();
    // TODO: Add barriers/state transitions for DX12/Vulkan support (using "cmd_list->barrier()")
    // Copy current frame into the CPU-accessible texture
    cmd_list->copy_resource(swapchain->get_current_back_buffer(), data.host_resource);
    // Very slow ... but ensures the copy has completed before accessing the data next
    queue->wait_idle();

    // Map CPU-accessible texture to read the data
    reshade::api::subresource_data host_data;
    if (!device->map_texture_region(
            data.host_resource, 0, nullptr, reshade::api::map_access::read_only, &host_data))
        return;

    const reshade::api::resource_desc desc = device->get_resource_desc(data.host_resource);

    // TODO: This assumes that the format is RGBA8, need to handle differently for different formats
    assert(desc.texture.format == reshade::api::format::r8g8b8a8_unorm);
    for (int y = 0; y < desc.texture.height; ++y)
    {
        for (int x = 0; x < desc.texture.width; ++x)
        {
            const size_t host_data_index = y * host_data.row_pitch + x * 4;

            const uint8_t r = static_cast<const uint8_t *>;(host_data.data)[host_data_index + 0];
            const uint8_t g = static_cast<const uint8_t *>;(host_data.data)[host_data_index + 1];
            const uint8_t b = static_cast<const uint8_t *>;(host_data.data)[host_data_index + 2];
            const uint8_t a = static_cast<const uint8_t *>;(host_data.data)[host_data_index + 3];

            // TODO: Do something with the pixel, e.g. dump this whole image to an image file
        }
    }

    device->unmap_texture_region(data.host_resource, 0);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        if (!reshade::register_addon(hinstDLL))
            return FALSE;
        reshade::register_event<reshade::addon_event::init_swapchain>;(on_init);
        reshade::register_event<reshade::addon_event::destroy_swapchain>;(on_destroy);
        reshade::register_event<reshade::addon_event::present>;(on_present);
        break;
    case DLL_PROCESS_DETACH:
        reshade::unregister_addon(hinstDLL);
        break;
    }
    return TRUE;
}
Last edit: 1 month 3 weeks ago by crosire.

Please Log in or Create an account to join the conversation.

More
1 month 2 days ago #3 by crosire
Replied by crosire on topic Capture final frame color buffer to file
There is a fully fledged example using ffmpeg here now: github.com/crosire/reshade/blob/main/exa...re/video_capture.cpp

Please Log in or Create an account to join the conversation.