Present the first (black) image
This commit is contained in:
parent
e989c2b406
commit
1e49b14879
@ -260,7 +260,7 @@ rt_result RT_RENDERER_API_FN(SubmitCommandBuffers)(rt_gpu_queue queue,
|
|||||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
|
||||||
.semaphore = rtGetSemaphore(info->wait_semaphores[i]),
|
.semaphore = rtGetSemaphore(info->wait_semaphores[i]),
|
||||||
.value = info->wait_values[i],
|
.value = info->wait_values[i],
|
||||||
.stageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||||
.deviceIndex = 0,
|
.deviceIndex = 0,
|
||||||
};
|
};
|
||||||
wait_semaphores[i] = semaphore_info;
|
wait_semaphores[i] = semaphore_info;
|
||||||
@ -270,7 +270,7 @@ rt_result RT_RENDERER_API_FN(SubmitCommandBuffers)(rt_gpu_queue queue,
|
|||||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
|
||||||
.semaphore = rtGetSemaphore(info->signal_semaphores[i]),
|
.semaphore = rtGetSemaphore(info->signal_semaphores[i]),
|
||||||
.value = info->signal_values[i],
|
.value = info->signal_values[i],
|
||||||
.stageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||||
.deviceIndex = 0,
|
.deviceIndex = 0,
|
||||||
};
|
};
|
||||||
wait_semaphores[i] = semaphore_info;
|
wait_semaphores[i] = semaphore_info;
|
||||||
|
@ -1,9 +1,59 @@
|
|||||||
#include "gpu.h"
|
|
||||||
#include "command_buffers.h"
|
#include "command_buffers.h"
|
||||||
|
#include "gpu.h"
|
||||||
|
#include "swapchain.h"
|
||||||
|
|
||||||
#include "runtime/renderer_api.h"
|
#include "runtime/renderer_api.h"
|
||||||
|
|
||||||
|
#define ONE_SECOND_NS 1000000000u
|
||||||
|
|
||||||
void RT_RENDERER_API_FN(BeginFrame)(unsigned int frame_id) {
|
void RT_RENDERER_API_FN(BeginFrame)(unsigned int frame_id) {
|
||||||
g_gpu.current_frame_id = frame_id;
|
g_gpu.current_frame_id = frame_id;
|
||||||
|
|
||||||
|
rt_frame_data *frame = rtGetFrameData(frame_id);
|
||||||
|
|
||||||
|
/* Wait until the previous frame is done */
|
||||||
|
VkFence fence = g_swapchain.image_fences[frame_id % g_swapchain.image_count];
|
||||||
|
RT_VK_CHECK(vkWaitForFences(g_gpu.device, 1, &fence, VK_TRUE, ONE_SECOND_NS));
|
||||||
|
RT_VK_CHECK(vkResetFences(g_gpu.device, 1, &fence));
|
||||||
|
|
||||||
rtResetCommandPools(frame_id);
|
rtResetCommandPools(frame_id);
|
||||||
|
|
||||||
|
VkResult acquire_res = vkAcquireNextImageKHR(g_gpu.device,
|
||||||
|
g_swapchain.swapchain,
|
||||||
|
ONE_SECOND_NS,
|
||||||
|
frame->image_available,
|
||||||
|
fence,
|
||||||
|
&frame->swapchain_image_index);
|
||||||
|
if (acquire_res == VK_SUBOPTIMAL_KHR || acquire_res == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||||
|
/* We need to recreate the swapchain and try again */
|
||||||
|
rtLog("vk", "Swapchain has become suboptimal and needs to be re-created.");
|
||||||
|
vkDeviceWaitIdle(g_gpu.device);
|
||||||
|
if (rtRecreateSwapchain() != RT_SUCCESS) {
|
||||||
|
rtReportError("vk", "Failed to recreate the swapchain.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rtRenBeginFrame(frame_id);
|
||||||
|
} else if (acquire_res != VK_SUCCESS) {
|
||||||
|
rtReportError("vk", "vkAcquireNextImageKHR failed: %u", acquire_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RT_RENDERER_API_FN(EndFrame)(unsigned int frame_id) {
|
||||||
|
rt_frame_data *frame = rtGetFrameData(frame_id);
|
||||||
|
|
||||||
|
uint32_t image_index = frame->swapchain_image_index;
|
||||||
|
|
||||||
|
VkPresentInfoKHR present_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||||
|
.pImageIndices = &image_index,
|
||||||
|
.pSwapchains = &g_swapchain.swapchain,
|
||||||
|
.swapchainCount = 1,
|
||||||
|
.pWaitSemaphores = &frame->image_available,
|
||||||
|
.waitSemaphoreCount = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
VkResult res = vkQueuePresentKHR(g_gpu.present_queue, &present_info);
|
||||||
|
if (res != VK_SUCCESS) {
|
||||||
|
rtReportError("vk", "vkQueuePresentKHR failed: %u", res);
|
||||||
|
}
|
||||||
}
|
}
|
@ -33,6 +33,12 @@ typedef struct {
|
|||||||
#endif
|
#endif
|
||||||
} rt_native_window;
|
} rt_native_window;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t swapchain_image_index;
|
||||||
|
VkSemaphore image_available;
|
||||||
|
VkSemaphore render_finished;
|
||||||
|
} rt_frame_data;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
VkInstance instance;
|
VkInstance instance;
|
||||||
VkDebugUtilsMessengerEXT messenger;
|
VkDebugUtilsMessengerEXT messenger;
|
||||||
@ -60,14 +66,30 @@ typedef struct {
|
|||||||
|
|
||||||
unsigned int max_frames_in_flight;
|
unsigned int max_frames_in_flight;
|
||||||
unsigned int current_frame_id;
|
unsigned int current_frame_id;
|
||||||
|
|
||||||
|
rt_frame_data frames[RT_VK_MAX_SUPPORTED_FRAMES_IN_FLIGHT];
|
||||||
} rt_vk_gpu;
|
} rt_vk_gpu;
|
||||||
|
|
||||||
#ifndef RT_VK_DONT_DEFINE_GPU_GLOBAL
|
#ifndef RT_VK_DONT_DEFINE_GPU_GLOBAL
|
||||||
extern rt_vk_gpu g_gpu;
|
extern rt_vk_gpu g_gpu;
|
||||||
|
|
||||||
|
|
||||||
|
RT_INLINE rt_frame_data *rtGetFrameData(unsigned int frame_id) {
|
||||||
|
return &g_gpu.frames[frame_id % g_gpu.max_frames_in_flight];
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Helper functions */
|
/* Helper functions */
|
||||||
|
|
||||||
|
#define RT_VK_CHECK(expr) \
|
||||||
|
do { \
|
||||||
|
VkResult res = expr; \
|
||||||
|
if (res != VK_SUCCESS) { \
|
||||||
|
rtReportError("vk", "Vulkan command failed with error %u.\nCommand: %s", res, #expr); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
VkFormat rtPixelFormatToVkFormat(rt_pixel_format format);
|
VkFormat rtPixelFormatToVkFormat(rt_pixel_format format);
|
||||||
|
|
||||||
VkSampleCountFlagBits rtSampleCountToFlags(unsigned int count);
|
VkSampleCountFlagBits rtSampleCountToFlags(unsigned int count);
|
||||||
|
@ -543,6 +543,34 @@ static void DestroyAllocator(void) {
|
|||||||
vmaDestroyAllocator(g_gpu.allocator);
|
vmaDestroyAllocator(g_gpu.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static rt_result CreatePerFrameObjects(void) {
|
||||||
|
for (unsigned int i = 0; i < g_gpu.max_frames_in_flight; ++i) {
|
||||||
|
VkSemaphoreCreateInfo semaphore_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||||
|
};
|
||||||
|
if (vkCreateSemaphore(g_gpu.device,
|
||||||
|
&semaphore_info,
|
||||||
|
g_gpu.alloc_cb,
|
||||||
|
&g_gpu.frames[i].render_finished) != VK_SUCCESS) {
|
||||||
|
return RT_UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
if (vkCreateSemaphore(g_gpu.device,
|
||||||
|
&semaphore_info,
|
||||||
|
g_gpu.alloc_cb,
|
||||||
|
&g_gpu.frames[i].image_available) != VK_SUCCESS) {
|
||||||
|
return RT_UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyPerFrameObjects(void) {
|
||||||
|
for (unsigned int i = 0; i < g_gpu.max_frames_in_flight; ++i) {
|
||||||
|
vkDestroySemaphore(g_gpu.device, g_gpu.frames[i].image_available, g_gpu.alloc_cb);
|
||||||
|
vkDestroySemaphore(g_gpu.device, g_gpu.frames[i].render_finished, g_gpu.alloc_cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern rt_result InitPipelineManagement(void);
|
extern rt_result InitPipelineManagement(void);
|
||||||
extern void ShutdownPipelineManagement(void);
|
extern void ShutdownPipelineManagement(void);
|
||||||
extern rt_result InitRenderTargetManagement(void);
|
extern rt_result InitRenderTargetManagement(void);
|
||||||
@ -582,6 +610,9 @@ rt_result RT_RENDERER_API_FN(Init)(const rt_renderer_init_info *info) {
|
|||||||
if (res != RT_SUCCESS)
|
if (res != RT_SUCCESS)
|
||||||
return res;
|
return res;
|
||||||
res = CreateAllocator();
|
res = CreateAllocator();
|
||||||
|
if (res != RT_SUCCESS)
|
||||||
|
return res;
|
||||||
|
res = CreatePerFrameObjects();
|
||||||
if (res != RT_SUCCESS)
|
if (res != RT_SUCCESS)
|
||||||
return res;
|
return res;
|
||||||
res = InitPipelineManagement();
|
res = InitPipelineManagement();
|
||||||
@ -611,8 +642,12 @@ void RT_RENDERER_API_FN(Shutdown)(void) {
|
|||||||
ShutdownSemaphoreManagement();
|
ShutdownSemaphoreManagement();
|
||||||
ShutdownRenderTargetManagement();
|
ShutdownRenderTargetManagement();
|
||||||
ShutdownPipelineManagement();
|
ShutdownPipelineManagement();
|
||||||
|
DestroyPerFrameObjects();
|
||||||
DestroyAllocator();
|
DestroyAllocator();
|
||||||
vkDestroyDevice(g_gpu.device, g_gpu.alloc_cb);
|
vkDestroyDevice(g_gpu.device, g_gpu.alloc_cb);
|
||||||
vkDestroySurfaceKHR(g_gpu.instance, g_gpu.surface, g_gpu.alloc_cb);
|
vkDestroySurfaceKHR(g_gpu.instance, g_gpu.surface, g_gpu.alloc_cb);
|
||||||
|
#ifdef RT_DEBUG
|
||||||
|
vkDestroyDebugUtilsMessengerEXT(g_gpu.instance, g_gpu.messenger, g_gpu.alloc_cb);
|
||||||
|
#endif
|
||||||
vkDestroyInstance(g_gpu.instance, g_gpu.alloc_cb);
|
vkDestroyInstance(g_gpu.instance, g_gpu.alloc_cb);
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,23 @@ rt_result rtCreateSwapchain(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Create fences */
|
||||||
|
for (uint32_t i = 0; i < g_swapchain.image_count; ++i) {
|
||||||
|
VkFenceCreateInfo fence_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||||
|
/* Create as signalled so that we can wait on it the first time we render to that
|
||||||
|
swapchain image. */
|
||||||
|
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||||
|
};
|
||||||
|
if (vkCreateFence(g_gpu.device,
|
||||||
|
&fence_info,
|
||||||
|
g_gpu.alloc_cb,
|
||||||
|
&g_swapchain.image_fences[i]) != VK_SUCCESS) {
|
||||||
|
rtReportError("vk", "Failed to create a fence for the swapchain");
|
||||||
|
return 53;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return RT_SUCCESS;
|
return RT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +195,7 @@ rt_result rtRecreateSwapchain(void) {
|
|||||||
|
|
||||||
void rtDestroySwapchain(void) {
|
void rtDestroySwapchain(void) {
|
||||||
for (uint32_t i = 0; i < g_swapchain.image_count; ++i) {
|
for (uint32_t i = 0; i < g_swapchain.image_count; ++i) {
|
||||||
|
vkDestroyFence(g_gpu.device, g_swapchain.image_fences[i], g_gpu.alloc_cb);
|
||||||
vkDestroyImageView(g_gpu.device, g_swapchain.image_views[i], g_gpu.alloc_cb);
|
vkDestroyImageView(g_gpu.device, g_swapchain.image_views[i], g_gpu.alloc_cb);
|
||||||
}
|
}
|
||||||
vkDestroySwapchainKHR(g_gpu.device, g_swapchain.swapchain, g_gpu.alloc_cb);
|
vkDestroySwapchainKHR(g_gpu.device, g_swapchain.swapchain, g_gpu.alloc_cb);
|
||||||
|
@ -11,6 +11,7 @@ typedef struct {
|
|||||||
VkSwapchainKHR swapchain;
|
VkSwapchainKHR swapchain;
|
||||||
VkImage images[RT_VK_MAX_SWAPCHAIN_IMAGES];
|
VkImage images[RT_VK_MAX_SWAPCHAIN_IMAGES];
|
||||||
VkImageView image_views[RT_VK_MAX_SWAPCHAIN_IMAGES];
|
VkImageView image_views[RT_VK_MAX_SWAPCHAIN_IMAGES];
|
||||||
|
VkFence image_fences[RT_VK_MAX_SWAPCHAIN_IMAGES];
|
||||||
uint32_t image_count;
|
uint32_t image_count;
|
||||||
VkFormat format;
|
VkFormat format;
|
||||||
VkExtent2D extent;
|
VkExtent2D extent;
|
||||||
|
@ -63,6 +63,8 @@ RT_DLLEXPORT void rtShutdownGFX(void);
|
|||||||
|
|
||||||
RT_DLLEXPORT void rtBeginGFXFrame(unsigned int frame_id);
|
RT_DLLEXPORT void rtBeginGFXFrame(unsigned int frame_id);
|
||||||
|
|
||||||
|
RT_DLLEXPORT void rtEndGFXFrame(unsigned int frame_id);
|
||||||
|
|
||||||
/* *********************************************************************
|
/* *********************************************************************
|
||||||
* Framegraph API
|
* Framegraph API
|
||||||
*
|
*
|
||||||
|
@ -26,6 +26,7 @@ extern void RT_RENDERER_API_FN(RegisterCVars)(void);
|
|||||||
extern rt_result RT_RENDERER_API_FN(Init)(const rt_renderer_init_info *);
|
extern rt_result RT_RENDERER_API_FN(Init)(const rt_renderer_init_info *);
|
||||||
extern void RT_RENDERER_API_FN(Shutdown)(void);
|
extern void RT_RENDERER_API_FN(Shutdown)(void);
|
||||||
extern void RT_RENDERER_API_FN(BeginFrame)(unsigned int);
|
extern void RT_RENDERER_API_FN(BeginFrame)(unsigned int);
|
||||||
|
extern void RT_RENDERER_API_FN(EndFrame)(unsigned int);
|
||||||
extern rt_pipeline_handle RT_RENDERER_API_FN(CompilePipeline)(const rt_pipeline_info *);
|
extern rt_pipeline_handle RT_RENDERER_API_FN(CompilePipeline)(const rt_pipeline_info *);
|
||||||
extern void RT_RENDERER_API_FN(DestroyPipeline)(rt_pipeline_handle);
|
extern void RT_RENDERER_API_FN(DestroyPipeline)(rt_pipeline_handle);
|
||||||
extern rt_render_target_handle
|
extern rt_render_target_handle
|
||||||
@ -43,7 +44,7 @@ extern void RT_RENDERER_API_FN(DestroySemaphores)(uint32_t count, rt_gpu_semapho
|
|||||||
extern uint64_t RT_RENDERER_API_FN(GetSemaphoreValue)(rt_gpu_semaphore_handle);
|
extern uint64_t RT_RENDERER_API_FN(GetSemaphoreValue)(rt_gpu_semaphore_handle);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern rt_result InitFramegraphManager(void);
|
extern rt_result InitFramegraphManager(void);
|
||||||
extern void ShutdownFramegraphManager(void);
|
extern void ShutdownFramegraphManager(void);
|
||||||
|
|
||||||
static bool LoadRenderer(void) {
|
static bool LoadRenderer(void) {
|
||||||
@ -68,6 +69,7 @@ static bool LoadRenderer(void) {
|
|||||||
RETRIEVE_SYMBOL(Init, rt_init_renderer_fn);
|
RETRIEVE_SYMBOL(Init, rt_init_renderer_fn);
|
||||||
RETRIEVE_SYMBOL(Shutdown, rt_shutdown_renderer_fn);
|
RETRIEVE_SYMBOL(Shutdown, rt_shutdown_renderer_fn);
|
||||||
RETRIEVE_SYMBOL(BeginFrame, rt_begin_frame_fn);
|
RETRIEVE_SYMBOL(BeginFrame, rt_begin_frame_fn);
|
||||||
|
RETRIEVE_SYMBOL(EndFrame, rt_end_frame_fn);
|
||||||
RETRIEVE_SYMBOL(CompilePipeline, rt_compile_pipeline_fn);
|
RETRIEVE_SYMBOL(CompilePipeline, rt_compile_pipeline_fn);
|
||||||
RETRIEVE_SYMBOL(DestroyPipeline, rt_destroy_pipeline_fn);
|
RETRIEVE_SYMBOL(DestroyPipeline, rt_destroy_pipeline_fn);
|
||||||
RETRIEVE_SYMBOL(CreateRenderTarget, rt_create_render_target_fn);
|
RETRIEVE_SYMBOL(CreateRenderTarget, rt_create_render_target_fn);
|
||||||
@ -90,6 +92,7 @@ static bool LoadRenderer(void) {
|
|||||||
g_renderer.Init = &rtRenInit;
|
g_renderer.Init = &rtRenInit;
|
||||||
g_renderer.Shutdown = &rtRenShutdown;
|
g_renderer.Shutdown = &rtRenShutdown;
|
||||||
g_renderer.BeginFrame = &rtRenBeginFrame;
|
g_renderer.BeginFrame = &rtRenBeginFrame;
|
||||||
|
g_renderer.EndFrame = &rtRenEndFrame;
|
||||||
g_renderer.CompilePipeline = &rtRenCompilePipeline;
|
g_renderer.CompilePipeline = &rtRenCompilePipeline;
|
||||||
g_renderer.DestroyPipeline = &rtRenDestroyPipeline;
|
g_renderer.DestroyPipeline = &rtRenDestroyPipeline;
|
||||||
g_renderer.CreateRenderTarget = &rtRenCreateRenderTarget;
|
g_renderer.CreateRenderTarget = &rtRenCreateRenderTarget;
|
||||||
@ -137,4 +140,8 @@ RT_DLLEXPORT void rtShutdownGFX(void) {
|
|||||||
|
|
||||||
RT_DLLEXPORT void rtBeginGFXFrame(unsigned int frame_id) {
|
RT_DLLEXPORT void rtBeginGFXFrame(unsigned int frame_id) {
|
||||||
g_renderer.BeginFrame(frame_id);
|
g_renderer.BeginFrame(frame_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
RT_DLLEXPORT void rtEndGFXFrame(unsigned int frame_id) {
|
||||||
|
g_renderer.EndFrame(frame_id);
|
||||||
}
|
}
|
@ -40,6 +40,7 @@ void RenderThreadEntry(void *param) {
|
|||||||
|
|
||||||
rtBeginGFXFrame(g_main_loop.r_frame_id);
|
rtBeginGFXFrame(g_main_loop.r_frame_id);
|
||||||
(g_main_loop.GameRender)();
|
(g_main_loop.GameRender)();
|
||||||
|
rtEndGFXFrame(g_main_loop.r_frame_id);
|
||||||
|
|
||||||
rtLog("RT", "Finished %u", g_main_loop.r_frame_id);
|
rtLog("RT", "Finished %u", g_main_loop.r_frame_id);
|
||||||
g_main_loop.r_frame_id += 1;
|
g_main_loop.r_frame_id += 1;
|
||||||
|
@ -140,6 +140,7 @@ typedef void rt_register_renderer_cvars_fn(void);
|
|||||||
typedef rt_result rt_init_renderer_fn(const rt_renderer_init_info *info);
|
typedef rt_result rt_init_renderer_fn(const rt_renderer_init_info *info);
|
||||||
typedef void rt_shutdown_renderer_fn(void);
|
typedef void rt_shutdown_renderer_fn(void);
|
||||||
typedef void rt_begin_frame_fn(unsigned int frame_id);
|
typedef void rt_begin_frame_fn(unsigned int frame_id);
|
||||||
|
typedef void rt_end_frame_fn(unsigned int frame_id);
|
||||||
typedef rt_pipeline_handle rt_compile_pipeline_fn(const rt_pipeline_info *info);
|
typedef rt_pipeline_handle rt_compile_pipeline_fn(const rt_pipeline_info *info);
|
||||||
typedef void rt_destroy_pipeline_fn(rt_pipeline_handle handle);
|
typedef void rt_destroy_pipeline_fn(rt_pipeline_handle handle);
|
||||||
typedef rt_render_target_handle rt_create_render_target_fn(const rt_render_target_info *info);
|
typedef rt_render_target_handle rt_create_render_target_fn(const rt_render_target_info *info);
|
||||||
@ -160,6 +161,7 @@ typedef struct {
|
|||||||
rt_init_renderer_fn *Init;
|
rt_init_renderer_fn *Init;
|
||||||
rt_shutdown_renderer_fn *Shutdown;
|
rt_shutdown_renderer_fn *Shutdown;
|
||||||
rt_begin_frame_fn *BeginFrame;
|
rt_begin_frame_fn *BeginFrame;
|
||||||
|
rt_end_frame_fn *EndFrame;
|
||||||
rt_compile_pipeline_fn *CompilePipeline;
|
rt_compile_pipeline_fn *CompilePipeline;
|
||||||
rt_destroy_pipeline_fn *DestroyPipeline;
|
rt_destroy_pipeline_fn *DestroyPipeline;
|
||||||
rt_create_render_target_fn *CreateRenderTarget;
|
rt_create_render_target_fn *CreateRenderTarget;
|
||||||
|
Loading…
Reference in New Issue
Block a user