966 lines
36 KiB
C++
966 lines
36 KiB
C++
/**
|
|
* Viewport Server Implementation - Shared Memory Framebuffer Export
|
|
*
|
|
*/
|
|
|
|
#include "viewport_server.h"
|
|
#include <kinc/log.h>
|
|
#include <kinc/window.h>
|
|
#include <kinc/graphics4/graphics.h>
|
|
#include <kinc/graphics4/rendertarget.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#ifdef KORE_WINDOWS
|
|
#include <malloc.h>
|
|
#endif
|
|
|
|
#ifdef KORE_WINDOWS
|
|
#include <Windows.h>
|
|
#else
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#ifdef KORE_DIRECT3D11
|
|
#include <d3d11.h>
|
|
#include <dxgi.h>
|
|
|
|
// Forward declare structures from Kinc's Direct3D11.h
|
|
#define MAXIMUM_WINDOWS 16
|
|
|
|
struct dx_window {
|
|
HWND hwnd;
|
|
IDXGISwapChain *swapChain;
|
|
ID3D11Texture2D *backBuffer;
|
|
ID3D11RenderTargetView *renderTargetView;
|
|
ID3D11Texture2D *depthStencil;
|
|
ID3D11DepthStencilView *depthStencilView;
|
|
int width;
|
|
int height;
|
|
int new_width;
|
|
int new_height;
|
|
bool vsync;
|
|
int depth_bits;
|
|
int stencil_bits;
|
|
};
|
|
|
|
struct dx_context {
|
|
ID3D11Device *device;
|
|
ID3D11DeviceContext *context;
|
|
IDXGIDevice *dxgiDevice;
|
|
IDXGIAdapter *dxgiAdapter;
|
|
IDXGIFactory *dxgiFactory;
|
|
int current_window;
|
|
struct dx_window windows[MAXIMUM_WINDOWS];
|
|
};
|
|
|
|
// declare with C linkage to match Kinc C definition
|
|
extern "C" struct dx_context dx_ctx;
|
|
#endif // KORE_DIRECT3D11
|
|
|
|
#ifdef KORE_DIRECT3D12
|
|
#include <d3d12.h>
|
|
#include <dxgi1_4.h>
|
|
#include <kinc/backend/graphics5/d3d12mini.h>
|
|
|
|
// D3D12 uses Graphics5 layer - access device and command queue
|
|
extern "C" ID3D12Device *device;
|
|
extern "C" ID3D12CommandQueue *commandQueue;
|
|
extern "C" ID3D12GraphicsCommandList *commandList;
|
|
#endif // KORE_DIRECT3D12
|
|
|
|
#ifdef KORE_OPENGL
|
|
#include <kinc/backend/graphics4/ogl.h>
|
|
#endif // KORE_OPENGL
|
|
|
|
#ifdef KORE_VULKAN
|
|
#include <vulkan/vulkan.h>
|
|
#include <kinc/backend/graphics5/vulkan.h>
|
|
#endif // KORE_VULKAN
|
|
|
|
// global viewport server state
|
|
static ViewportServerState g_viewport_state = {0};
|
|
|
|
// ============================================================================
|
|
// Platform-specific Shared Memory Implementation
|
|
// ============================================================================
|
|
|
|
#ifdef KORE_WINDOWS
|
|
|
|
static bool shmem_create_windows(const char* name, size_t size) {
|
|
// Create file mapping
|
|
HANDLE hMapFile = CreateFileMappingA(
|
|
INVALID_HANDLE_VALUE,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
(DWORD)(size >> 32),
|
|
(DWORD)(size & 0xFFFFFFFF),
|
|
name
|
|
);
|
|
|
|
if (hMapFile == NULL) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create shared memory: %lu", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
void* pMem = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, size);
|
|
if (pMem == NULL) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to map shared memory: %lu", GetLastError());
|
|
CloseHandle(hMapFile);
|
|
return false;
|
|
}
|
|
|
|
g_viewport_state.shmem_handle = hMapFile;
|
|
g_viewport_state.shmem_ptr = pMem;
|
|
g_viewport_state.shmem_size = size;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)pMem;
|
|
memset(header, 0, sizeof(SharedFramebufferHeader));
|
|
header->magic = VIEWPORT_MAGIC;
|
|
header->version = VIEWPORT_VERSION;
|
|
header->width = g_viewport_state.width;
|
|
header->height = g_viewport_state.height;
|
|
header->frame_id = 0;
|
|
header->ready_flag = 0;
|
|
header->format = 0; // RGBA8
|
|
|
|
kinc_log(KINC_LOG_LEVEL_INFO, "Windows shared memory created: %s (%zu bytes)", name, size);
|
|
return true;
|
|
}
|
|
|
|
static void shmem_destroy_windows(void) {
|
|
if (g_viewport_state.shmem_ptr) {
|
|
UnmapViewOfFile(g_viewport_state.shmem_ptr);
|
|
g_viewport_state.shmem_ptr = NULL;
|
|
}
|
|
if (g_viewport_state.shmem_handle) {
|
|
CloseHandle((HANDLE)g_viewport_state.shmem_handle);
|
|
g_viewport_state.shmem_handle = NULL;
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
static bool shmem_create_posix(const char* name, size_t size) {
|
|
char shmem_name[256];
|
|
if (name[0] != '/') {
|
|
snprintf(shmem_name, sizeof(shmem_name), "/%s", name);
|
|
} else {
|
|
strncpy(shmem_name, name, sizeof(shmem_name) - 1);
|
|
}
|
|
|
|
// remove already existing if any
|
|
shm_unlink(shmem_name);
|
|
|
|
// shared memory object
|
|
int fd = shm_open(shmem_name, O_CREAT | O_RDWR, 0666);
|
|
if (fd == -1) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create shared memory: %s", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
if (ftruncate(fd, size) == -1) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to set shared memory size: %s", strerror(errno));
|
|
close(fd);
|
|
shm_unlink(shmem_name);
|
|
return false;
|
|
}
|
|
|
|
// map the memory
|
|
void* pMem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (pMem == MAP_FAILED) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to map shared memory: %s", strerror(errno));
|
|
close(fd);
|
|
shm_unlink(shmem_name);
|
|
return false;
|
|
}
|
|
|
|
g_viewport_state.shmem_handle = (void*)(intptr_t)fd;
|
|
g_viewport_state.shmem_ptr = pMem;
|
|
g_viewport_state.shmem_size = size;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)pMem;
|
|
memset(header, 0, sizeof(SharedFramebufferHeader));
|
|
header->magic = VIEWPORT_MAGIC;
|
|
header->version = VIEWPORT_VERSION;
|
|
header->width = g_viewport_state.width;
|
|
header->height = g_viewport_state.height;
|
|
header->frame_id = 0;
|
|
header->ready_flag = 0;
|
|
header->format = 0; // RGBA8
|
|
|
|
kinc_log(KINC_LOG_LEVEL_INFO, "POSIX shared memory created: %s (%zu bytes)", shmem_name, size);
|
|
return true;
|
|
}
|
|
|
|
static void shmem_destroy_posix(void) {
|
|
if (g_viewport_state.shmem_ptr) {
|
|
munmap(g_viewport_state.shmem_ptr, g_viewport_state.shmem_size);
|
|
g_viewport_state.shmem_ptr = NULL;
|
|
}
|
|
if (g_viewport_state.shmem_handle) {
|
|
close((int)(intptr_t)g_viewport_state.shmem_handle);
|
|
g_viewport_state.shmem_handle = NULL;
|
|
|
|
// unlink shared memory
|
|
char shmem_name[256];
|
|
if (g_viewport_state.shmem_name[0] != '/') {
|
|
snprintf(shmem_name, sizeof(shmem_name), "/%s", g_viewport_state.shmem_name);
|
|
} else {
|
|
strncpy(shmem_name, g_viewport_state.shmem_name, sizeof(shmem_name) - 1);
|
|
}
|
|
shm_unlink(shmem_name);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static bool shmem_create(const char* name, size_t size) {
|
|
#ifdef KORE_WINDOWS
|
|
return shmem_create_windows(name, size);
|
|
#else
|
|
return shmem_create_posix(name, size);
|
|
#endif
|
|
}
|
|
|
|
static void shmem_destroy(void) {
|
|
#ifdef KORE_WINDOWS
|
|
shmem_destroy_windows();
|
|
#else
|
|
shmem_destroy_posix();
|
|
#endif
|
|
}
|
|
|
|
bool viewport_server_init(const char* shmem_name, int width, int height) {
|
|
if (g_viewport_state.initialized) {
|
|
kinc_log(KINC_LOG_LEVEL_WARNING, "Viewport server already initialized");
|
|
return true;
|
|
}
|
|
|
|
if (width <= 0 || width > VIEWPORT_MAX_WIDTH ||
|
|
height <= 0 || height > VIEWPORT_MAX_HEIGHT) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid viewport dimensions: %dx%d", width, height);
|
|
return false;
|
|
}
|
|
|
|
g_viewport_state.width = width;
|
|
g_viewport_state.height = height;
|
|
strncpy(g_viewport_state.shmem_name, shmem_name ? shmem_name : VIEWPORT_SHMEM_NAME,
|
|
sizeof(g_viewport_state.shmem_name) - 1);
|
|
|
|
// pre-allocate shared memory for MAXIMUM size to avoid recreation on resize
|
|
// *** this prevents access denied errors when Blender still has the memory mapped
|
|
size_t max_pixel_size = (size_t)VIEWPORT_MAX_WIDTH * VIEWPORT_MAX_HEIGHT * 4; // RGBA8
|
|
size_t total_size = VIEWPORT_HEADER_SIZE + max_pixel_size;
|
|
|
|
// create shared memory
|
|
if (!shmem_create(g_viewport_state.shmem_name, total_size)) {
|
|
return false;
|
|
}
|
|
|
|
// now allocate CPU side pixel buffer for readback with a max size for the resize support
|
|
g_viewport_state.pixel_buffer = (uint8_t*)malloc(max_pixel_size);
|
|
if (!g_viewport_state.pixel_buffer) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to allocate pixel buffer");
|
|
shmem_destroy();
|
|
return false;
|
|
}
|
|
|
|
// TODO: needs investigation
|
|
// clear pixel buffer to prevent stale data from previous scenes
|
|
memset(g_viewport_state.pixel_buffer, 0, max_pixel_size);
|
|
// also clear shared memory pixel area to prevent flickering with old data
|
|
uint8_t* pixel_dest = (uint8_t*)g_viewport_state.shmem_ptr + VIEWPORT_HEADER_SIZE;
|
|
memset(pixel_dest, 0, max_pixel_size);
|
|
|
|
kinc_g4_render_target_t* rt = (kinc_g4_render_target_t*)malloc(sizeof(kinc_g4_render_target_t));
|
|
if (!rt) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to allocate render target");
|
|
free(g_viewport_state.pixel_buffer);
|
|
shmem_destroy();
|
|
return false;
|
|
}
|
|
|
|
kinc_g4_render_target_init(rt, width, height, KINC_G4_RENDER_TARGET_FORMAT_32BIT, 24, 0);
|
|
g_viewport_state.render_target = rt;
|
|
|
|
g_viewport_state.enabled = true;
|
|
g_viewport_state.initialized = true;
|
|
g_viewport_state.frame_count = 0;
|
|
|
|
kinc_log(KINC_LOG_LEVEL_INFO, "Viewport server initialized: %dx%d, shmem=%s",
|
|
width, height, g_viewport_state.shmem_name);
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef KORE_DIRECT3D11
|
|
static ID3D11Texture2D* g_stagingTexture = NULL;
|
|
static int g_stagingWidth = 0, g_stagingHeight = 0;
|
|
#endif
|
|
|
|
#ifdef KORE_DIRECT3D12
|
|
static ID3D12Resource* g_d3d12ReadbackBuffer = NULL;
|
|
static size_t g_d3d12ReadbackSize = 0;
|
|
#endif
|
|
|
|
#ifdef KORE_VULKAN
|
|
static VkBuffer g_vkReadbackBuffer = VK_NULL_HANDLE;
|
|
static VkDeviceMemory g_vkReadbackMemory = VK_NULL_HANDLE;
|
|
static size_t g_vkReadbackSize = 0;
|
|
#endif
|
|
|
|
void viewport_server_shutdown(void) {
|
|
if (!g_viewport_state.initialized) {
|
|
return;
|
|
}
|
|
|
|
#ifdef KORE_DIRECT3D11
|
|
if (g_stagingTexture) {
|
|
g_stagingTexture->Release();
|
|
g_stagingTexture = NULL;
|
|
g_stagingWidth = 0;
|
|
g_stagingHeight = 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef KORE_DIRECT3D12
|
|
if (g_d3d12ReadbackBuffer) {
|
|
g_d3d12ReadbackBuffer->Release();
|
|
g_d3d12ReadbackBuffer = NULL;
|
|
g_d3d12ReadbackSize = 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef KORE_VULKAN
|
|
if (g_vkReadbackBuffer != VK_NULL_HANDLE) {
|
|
vkDestroyBuffer(vk_ctx.device, g_vkReadbackBuffer, NULL);
|
|
g_vkReadbackBuffer = VK_NULL_HANDLE;
|
|
}
|
|
if (g_vkReadbackMemory != VK_NULL_HANDLE) {
|
|
vkFreeMemory(vk_ctx.device, g_vkReadbackMemory, NULL);
|
|
g_vkReadbackMemory = VK_NULL_HANDLE;
|
|
}
|
|
g_vkReadbackSize = 0;
|
|
#endif
|
|
|
|
if (g_viewport_state.render_target) {
|
|
kinc_g4_render_target_destroy((kinc_g4_render_target_t*)g_viewport_state.render_target);
|
|
free(g_viewport_state.render_target);
|
|
g_viewport_state.render_target = NULL;
|
|
}
|
|
|
|
if (g_viewport_state.pixel_buffer) {
|
|
free(g_viewport_state.pixel_buffer);
|
|
g_viewport_state.pixel_buffer = NULL;
|
|
}
|
|
|
|
shmem_destroy();
|
|
|
|
g_viewport_state.enabled = false;
|
|
g_viewport_state.initialized = false;
|
|
g_viewport_state.frame_count = 0;
|
|
|
|
kinc_log(KINC_LOG_LEVEL_INFO, "Viewport server shutdown complete");
|
|
}
|
|
|
|
bool viewport_server_is_enabled(void) {
|
|
return g_viewport_state.enabled && g_viewport_state.initialized;
|
|
}
|
|
|
|
void viewport_server_begin_frame(void) {
|
|
// no-op we let iron render normally to framebuffer and capture the backbuffer in end_frame BEFORE swap_buffers
|
|
}
|
|
|
|
void viewport_server_end_frame(void) {
|
|
if (!viewport_server_is_enabled()) return;
|
|
|
|
if (g_viewport_state.pixel_buffer == NULL ||
|
|
g_viewport_state.shmem_ptr == NULL) {
|
|
kinc_log(KINC_LOG_LEVEL_WARNING, "Viewport server: invalid state in end_frame");
|
|
g_viewport_state.frame_count++;
|
|
return;
|
|
}
|
|
|
|
if (g_viewport_state.frame_count < 3) {
|
|
g_viewport_state.frame_count++;
|
|
return;
|
|
}
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
|
|
header->width = g_viewport_state.width;
|
|
header->height = g_viewport_state.height;
|
|
|
|
size_t pixel_size = (size_t)g_viewport_state.width * g_viewport_state.height * 4;
|
|
uint8_t* pixels = g_viewport_state.pixel_buffer;
|
|
uint8_t* pixel_dest = (uint8_t*)g_viewport_state.shmem_ptr + VIEWPORT_HEADER_SIZE;
|
|
|
|
// ====================================================================
|
|
// Backend-specific framebuffer readback
|
|
// ====================================================================
|
|
|
|
#if defined(KORE_DIRECT3D11)
|
|
// --- Direct3D 11: copy backbuffer to staging texture, then map ---
|
|
{
|
|
ID3D11DeviceContext* ctx = dx_ctx.context;
|
|
ID3D11Device* dev = dx_ctx.device;
|
|
struct dx_window* win = &dx_ctx.windows[0];
|
|
ID3D11Texture2D* backBuffer = win->backBuffer;
|
|
|
|
if (backBuffer && ctx && dev) {
|
|
D3D11_TEXTURE2D_DESC bbDesc;
|
|
backBuffer->GetDesc(&bbDesc);
|
|
|
|
int bbWidth = (int)bbDesc.Width;
|
|
int bbHeight = (int)bbDesc.Height;
|
|
|
|
// capture dimensions are the actual size of whats renderedso we clamp to max shared memory size
|
|
int captureWidth = bbWidth;
|
|
int captureHeight = bbHeight;
|
|
if (captureWidth > VIEWPORT_MAX_WIDTH) captureWidth = VIEWPORT_MAX_WIDTH;
|
|
if (captureHeight > VIEWPORT_MAX_HEIGHT) captureHeight = VIEWPORT_MAX_HEIGHT;
|
|
|
|
header->width = captureWidth;
|
|
header->height = captureHeight;
|
|
pixel_size = (size_t)captureWidth * captureHeight * 4;
|
|
|
|
// same dimensions between source and destination
|
|
if (!g_stagingTexture || g_stagingWidth != bbWidth || g_stagingHeight != bbHeight) {
|
|
if (g_stagingTexture) g_stagingTexture->Release();
|
|
|
|
D3D11_TEXTURE2D_DESC desc = {};
|
|
desc.Width = bbWidth;
|
|
desc.Height = bbHeight;
|
|
desc.MipLevels = 1;
|
|
desc.ArraySize = 1;
|
|
desc.Format = bbDesc.Format;
|
|
desc.SampleDesc.Count = 1;
|
|
desc.Usage = D3D11_USAGE_STAGING;
|
|
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
|
|
|
HRESULT hr = dev->CreateTexture2D(&desc, NULL, &g_stagingTexture);
|
|
if (FAILED(hr)) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "D3D11: Failed to create staging texture: 0x%08X", hr);
|
|
g_stagingTexture = NULL;
|
|
g_stagingWidth = 0;
|
|
g_stagingHeight = 0;
|
|
g_viewport_state.frame_count++;
|
|
return;
|
|
}
|
|
g_stagingWidth = bbWidth;
|
|
g_stagingHeight = bbHeight;
|
|
}
|
|
|
|
ctx->CopyResource(g_stagingTexture, backBuffer);
|
|
|
|
// only read the portion we need
|
|
D3D11_MAPPED_SUBRESOURCE mapped;
|
|
if (SUCCEEDED(ctx->Map(g_stagingTexture, 0, D3D11_MAP_READ, 0, &mapped))) {
|
|
for (int y = 0; y < captureHeight; y++) {
|
|
memcpy(pixels + y * captureWidth * 4,
|
|
(uint8_t*)mapped.pData + y * mapped.RowPitch,
|
|
captureWidth * 4);
|
|
}
|
|
ctx->Unmap(g_stagingTexture, 0);
|
|
|
|
// BGRA to RGBA conversion
|
|
for (size_t i = 0; i < pixel_size; i += 4) {
|
|
uint8_t t = pixels[i]; pixels[i] = pixels[i+2]; pixels[i+2] = t;
|
|
}
|
|
|
|
memcpy(pixel_dest, pixels, pixel_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
#elif defined(KORE_DIRECT3D12)
|
|
{
|
|
int captureWidth = g_viewport_state.width;
|
|
int captureHeight = g_viewport_state.height;
|
|
if (captureWidth > VIEWPORT_MAX_WIDTH) captureWidth = VIEWPORT_MAX_WIDTH;
|
|
if (captureHeight > VIEWPORT_MAX_HEIGHT) captureHeight = VIEWPORT_MAX_HEIGHT;
|
|
|
|
header->width = captureWidth;
|
|
header->height = captureHeight;
|
|
|
|
// aligned to D3D12_TEXTURE_DATA_PITCH_ALIGNMENT (256 bytes)
|
|
UINT rowPitch = (captureWidth * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1);
|
|
size_t readbackSize = (size_t)rowPitch * captureHeight;
|
|
pixel_size = (size_t)captureWidth * captureHeight * 4;
|
|
|
|
if (!g_d3d12ReadbackBuffer || g_d3d12ReadbackSize < readbackSize) {
|
|
if (g_d3d12ReadbackBuffer) g_d3d12ReadbackBuffer->Release();
|
|
|
|
D3D12_HEAP_PROPERTIES heapProps = {};
|
|
heapProps.Type = D3D12_HEAP_TYPE_READBACK;
|
|
|
|
D3D12_RESOURCE_DESC bufDesc = {};
|
|
bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
|
bufDesc.Width = readbackSize;
|
|
bufDesc.Height = 1;
|
|
bufDesc.DepthOrArraySize = 1;
|
|
bufDesc.MipLevels = 1;
|
|
bufDesc.SampleDesc.Count = 1;
|
|
bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
|
|
|
HRESULT hr = device->CreateCommittedResource(
|
|
&heapProps, D3D12_HEAP_FLAG_NONE, &bufDesc,
|
|
D3D12_RESOURCE_STATE_COPY_DEST, NULL,
|
|
IID_PPV_ARGS(&g_d3d12ReadbackBuffer));
|
|
if (FAILED(hr)) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "D3D12: Failed to create readback buffer: 0x%08X", hr);
|
|
g_d3d12ReadbackBuffer = NULL;
|
|
g_d3d12ReadbackSize = 0;
|
|
g_viewport_state.frame_count++;
|
|
return;
|
|
}
|
|
g_d3d12ReadbackSize = readbackSize;
|
|
}
|
|
|
|
ID3D12CommandAllocator* copyAllocator = NULL;
|
|
ID3D12GraphicsCommandList* copyCmd = NULL;
|
|
HRESULT hr = device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(©Allocator));
|
|
if (SUCCEEDED(hr)) {
|
|
hr = device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, copyAllocator, NULL, IID_PPV_ARGS(©Cmd));
|
|
}
|
|
|
|
if (SUCCEEDED(hr) && copyCmd) {
|
|
extern struct dx_window dx_windows[];
|
|
struct dx_window* win = &dx_windows[0];
|
|
ID3D12Resource* backBuffer = NULL;
|
|
|
|
if (win->swapChain) {
|
|
IDXGISwapChain3* swapChain3 = NULL;
|
|
if (SUCCEEDED(((IDXGISwapChain*)win->swapChain)->QueryInterface(IID_PPV_ARGS(&swapChain3)))) {
|
|
UINT backBufferIndex = swapChain3->GetCurrentBackBufferIndex();
|
|
swapChain3->GetBuffer(backBufferIndex, IID_PPV_ARGS(&backBuffer));
|
|
swapChain3->Release();
|
|
}
|
|
}
|
|
|
|
if (backBuffer) {
|
|
D3D12_RESOURCE_BARRIER barrier = {};
|
|
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
barrier.Transition.pResource = backBuffer;
|
|
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
|
|
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
|
|
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
|
copyCmd->ResourceBarrier(1, &barrier);
|
|
|
|
D3D12_TEXTURE_COPY_LOCATION srcLoc = {};
|
|
srcLoc.pResource = backBuffer;
|
|
srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
|
srcLoc.SubresourceIndex = 0;
|
|
|
|
D3D12_TEXTURE_COPY_LOCATION dstLoc = {};
|
|
dstLoc.pResource = g_d3d12ReadbackBuffer;
|
|
dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
|
dstLoc.PlacedFootprint.Offset = 0;
|
|
dstLoc.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
dstLoc.PlacedFootprint.Footprint.Width = captureWidth;
|
|
dstLoc.PlacedFootprint.Footprint.Height = captureHeight;
|
|
dstLoc.PlacedFootprint.Footprint.Depth = 1;
|
|
dstLoc.PlacedFootprint.Footprint.RowPitch = rowPitch;
|
|
|
|
copyCmd->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, NULL);
|
|
|
|
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE;
|
|
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
|
|
copyCmd->ResourceBarrier(1, &barrier);
|
|
|
|
copyCmd->Close();
|
|
|
|
ID3D12CommandList* lists[] = { copyCmd };
|
|
commandQueue->ExecuteCommandLists(1, lists);
|
|
|
|
ID3D12Fence* fence = NULL;
|
|
HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
if (SUCCEEDED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)))) {
|
|
commandQueue->Signal(fence, 1);
|
|
fence->SetEventOnCompletion(1, event);
|
|
WaitForSingleObject(event, 1000);
|
|
fence->Release();
|
|
}
|
|
CloseHandle(event);
|
|
|
|
void* mappedData = NULL;
|
|
D3D12_RANGE readRange = { 0, readbackSize };
|
|
if (SUCCEEDED(g_d3d12ReadbackBuffer->Map(0, &readRange, &mappedData))) {
|
|
for (int y = 0; y < captureHeight; y++) {
|
|
memcpy(pixels + y * captureWidth * 4,
|
|
(uint8_t*)mappedData + y * rowPitch,
|
|
captureWidth * 4);
|
|
}
|
|
D3D12_RANGE writeRange = { 0, 0 };
|
|
g_d3d12ReadbackBuffer->Unmap(0, &writeRange);
|
|
memcpy(pixel_dest, pixels, pixel_size);
|
|
}
|
|
|
|
backBuffer->Release();
|
|
}
|
|
}
|
|
|
|
if (copyCmd) copyCmd->Release();
|
|
if (copyAllocator) copyAllocator->Release();
|
|
}
|
|
|
|
#elif defined(KORE_OPENGL)
|
|
{
|
|
int captureWidth = g_viewport_state.width;
|
|
int captureHeight = g_viewport_state.height;
|
|
if (captureWidth > VIEWPORT_MAX_WIDTH) captureWidth = VIEWPORT_MAX_WIDTH;
|
|
if (captureHeight > VIEWPORT_MAX_HEIGHT) captureHeight = VIEWPORT_MAX_HEIGHT;
|
|
|
|
header->width = captureWidth;
|
|
header->height = captureHeight;
|
|
pixel_size = (size_t)captureWidth * captureHeight * 4;
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
glReadPixels(0, 0, captureWidth, captureHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
|
|
int stride = captureWidth * 4;
|
|
uint8_t* row_temp = (uint8_t*)alloca(stride);
|
|
for (int y = 0; y < captureHeight / 2; y++) {
|
|
uint8_t* top = pixels + y * stride;
|
|
uint8_t* bot = pixels + (captureHeight - 1 - y) * stride;
|
|
memcpy(row_temp, top, stride);
|
|
memcpy(top, bot, stride);
|
|
memcpy(bot, row_temp, stride);
|
|
}
|
|
|
|
memcpy(pixel_dest, pixels, pixel_size);
|
|
}
|
|
|
|
#elif defined(KORE_VULKAN)
|
|
{
|
|
int captureWidth = g_viewport_state.width;
|
|
int captureHeight = g_viewport_state.height;
|
|
if (captureWidth > VIEWPORT_MAX_WIDTH) captureWidth = VIEWPORT_MAX_WIDTH;
|
|
if (captureHeight > VIEWPORT_MAX_HEIGHT) captureHeight = VIEWPORT_MAX_HEIGHT;
|
|
|
|
header->width = captureWidth;
|
|
header->height = captureHeight;
|
|
pixel_size = (size_t)captureWidth * captureHeight * 4;
|
|
|
|
if (g_vkReadbackBuffer == VK_NULL_HANDLE || g_vkReadbackSize < pixel_size) {
|
|
if (g_vkReadbackBuffer != VK_NULL_HANDLE) {
|
|
vkDestroyBuffer(vk_ctx.device, g_vkReadbackBuffer, NULL);
|
|
vkFreeMemory(vk_ctx.device, g_vkReadbackMemory, NULL);
|
|
}
|
|
|
|
VkBufferCreateInfo bufInfo = {};
|
|
bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
bufInfo.size = pixel_size;
|
|
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
|
|
if (vkCreateBuffer(vk_ctx.device, &bufInfo, NULL, &g_vkReadbackBuffer) != VK_SUCCESS) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Vulkan: Failed to create readback buffer");
|
|
g_vkReadbackBuffer = VK_NULL_HANDLE;
|
|
g_vkReadbackSize = 0;
|
|
g_viewport_state.frame_count++;
|
|
return;
|
|
}
|
|
|
|
VkMemoryRequirements memReqs;
|
|
vkGetBufferMemoryRequirements(vk_ctx.device, g_vkReadbackBuffer, &memReqs);
|
|
|
|
uint32_t memTypeIndex = UINT32_MAX;
|
|
for (uint32_t i = 0; i < vk_ctx.memory_properties.memoryTypeCount; i++) {
|
|
if ((memReqs.memoryTypeBits & (1 << i)) &&
|
|
(vk_ctx.memory_properties.memoryTypes[i].propertyFlags &
|
|
(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) ==
|
|
(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
|
|
memTypeIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (memTypeIndex == UINT32_MAX) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Vulkan: No suitable memory type for readback");
|
|
vkDestroyBuffer(vk_ctx.device, g_vkReadbackBuffer, NULL);
|
|
g_vkReadbackBuffer = VK_NULL_HANDLE;
|
|
g_vkReadbackSize = 0;
|
|
g_viewport_state.frame_count++;
|
|
return;
|
|
}
|
|
|
|
VkMemoryAllocateInfo allocInfo = {};
|
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
allocInfo.allocationSize = memReqs.size;
|
|
allocInfo.memoryTypeIndex = memTypeIndex;
|
|
|
|
if (vkAllocateMemory(vk_ctx.device, &allocInfo, NULL, &g_vkReadbackMemory) != VK_SUCCESS) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Vulkan: Failed to allocate readback memory");
|
|
vkDestroyBuffer(vk_ctx.device, g_vkReadbackBuffer, NULL);
|
|
g_vkReadbackBuffer = VK_NULL_HANDLE;
|
|
g_vkReadbackSize = 0;
|
|
g_viewport_state.frame_count++;
|
|
return;
|
|
}
|
|
|
|
vkBindBufferMemory(vk_ctx.device, g_vkReadbackBuffer, g_vkReadbackMemory, 0);
|
|
g_vkReadbackSize = pixel_size;
|
|
}
|
|
|
|
struct vk_window* win = &vk_ctx.windows[0];
|
|
if (win->images && win->current_image < win->image_count) {
|
|
VkImage swapImage = win->images[win->current_image];
|
|
|
|
VkCommandBufferAllocateInfo cmdAllocInfo = {};
|
|
cmdAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
cmdAllocInfo.commandPool = vk_ctx.cmd_pool;
|
|
cmdAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
cmdAllocInfo.commandBufferCount = 1;
|
|
|
|
VkCommandBuffer cmdBuf;
|
|
if (vkAllocateCommandBuffers(vk_ctx.device, &cmdAllocInfo, &cmdBuf) != VK_SUCCESS) {
|
|
g_viewport_state.frame_count++;
|
|
return;
|
|
}
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {};
|
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
vkBeginCommandBuffer(cmdBuf, &beginInfo);
|
|
|
|
VkImageMemoryBarrier barrier = {};
|
|
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
barrier.image = swapImage;
|
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
barrier.subresourceRange.baseMipLevel = 0;
|
|
barrier.subresourceRange.levelCount = 1;
|
|
barrier.subresourceRange.baseArrayLayer = 0;
|
|
barrier.subresourceRange.layerCount = 1;
|
|
|
|
vkCmdPipelineBarrier(cmdBuf,
|
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
0, 0, NULL, 0, NULL, 1, &barrier);
|
|
|
|
VkBufferImageCopy region = {};
|
|
region.bufferOffset = 0;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.mipLevel = 0;
|
|
region.imageSubresource.baseArrayLayer = 0;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageOffset = {0, 0, 0};
|
|
region.imageExtent = {(uint32_t)captureWidth, (uint32_t)captureHeight, 1};
|
|
|
|
vkCmdCopyImageToBuffer(cmdBuf, swapImage,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
g_vkReadbackBuffer, 1, ®ion);
|
|
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
|
|
vkCmdPipelineBarrier(cmdBuf,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
|
0, 0, NULL, 0, NULL, 1, &barrier);
|
|
|
|
vkEndCommandBuffer(cmdBuf);
|
|
|
|
VkSubmitInfo submitInfo = {};
|
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &cmdBuf;
|
|
|
|
vkQueueSubmit(vk_ctx.queue, 1, &submitInfo, VK_NULL_HANDLE);
|
|
vkQueueWaitIdle(vk_ctx.queue);
|
|
|
|
vkFreeCommandBuffers(vk_ctx.device, vk_ctx.cmd_pool, 1, &cmdBuf);
|
|
|
|
void* mappedData = NULL;
|
|
if (vkMapMemory(vk_ctx.device, g_vkReadbackMemory, 0, pixel_size, 0, &mappedData) == VK_SUCCESS) {
|
|
memcpy(pixels, mappedData, pixel_size);
|
|
vkUnmapMemory(vk_ctx.device, g_vkReadbackMemory);
|
|
|
|
if (win->format.format == VK_FORMAT_B8G8R8A8_UNORM ||
|
|
win->format.format == VK_FORMAT_B8G8R8A8_SRGB) {
|
|
for (size_t i = 0; i < pixel_size; i += 4) {
|
|
uint8_t t = pixels[i]; pixels[i] = pixels[i+2]; pixels[i+2] = t;
|
|
}
|
|
}
|
|
|
|
memcpy(pixel_dest, pixels, pixel_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
// other platforms use render target
|
|
if (g_viewport_state.render_target) {
|
|
kinc_g4_render_target_t* rt = (kinc_g4_render_target_t*)g_viewport_state.render_target;
|
|
kinc_g4_render_target_get_pixels(rt, pixels);
|
|
memcpy(pixel_dest, pixels, pixel_size);
|
|
}
|
|
#endif
|
|
|
|
g_viewport_state.frame_count++;
|
|
header->frame_id = g_viewport_state.frame_count;
|
|
|
|
// NOTE: Camera sync from RunT to Blender is handled via viewport_server_set_camera()
|
|
// which is called explicitly when RunT's internal camera changes.
|
|
// We do NOT extract camera from view_matrix here as that would create a feedback loop
|
|
// (Blender sends view_matrix -> we extract camera -> Blender applies it -> repeat)
|
|
|
|
// Memory barrier to ensure writes complete before setting ready flag
|
|
#ifdef KORE_WINDOWS
|
|
MemoryBarrier();
|
|
#else
|
|
__sync_synchronize();
|
|
#endif
|
|
|
|
header->ready_flag = 1;
|
|
}
|
|
|
|
bool viewport_server_check_resize(int* new_width, int* new_height) {
|
|
if (!viewport_server_is_enabled()) return false;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
|
|
if (header->resize_request) {
|
|
*new_width = header->viewport_width;
|
|
*new_height = header->viewport_height;
|
|
header->resize_request = 0;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool viewport_server_resize(int new_width, int new_height) {
|
|
if (!g_viewport_state.initialized) return false;
|
|
|
|
if (new_width <= 0 || new_width > VIEWPORT_MAX_WIDTH ||
|
|
new_height <= 0 || new_height > VIEWPORT_MAX_HEIGHT) {
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid resize dimensions: %dx%d", new_width, new_height);
|
|
return false;
|
|
}
|
|
|
|
if (new_width == g_viewport_state.width && new_height == g_viewport_state.height) {
|
|
return true;
|
|
}
|
|
|
|
kinc_log(KINC_LOG_LEVEL_INFO, "Viewport server resizing: %dx%d -> %dx%d",
|
|
g_viewport_state.width, g_viewport_state.height, new_width, new_height);
|
|
|
|
// resize the actual Kinc window so the D3D11 backbuffer is the correct size critical because viewport_server_end_frame captures from the backbuffer
|
|
kinc_window_resize(0, new_width, new_height);
|
|
|
|
if (g_viewport_state.render_target) {
|
|
kinc_g4_render_target_destroy((kinc_g4_render_target_t*)g_viewport_state.render_target);
|
|
// dont free here we reuse the allocation
|
|
}
|
|
|
|
// update dimensions pre-allocated and reinitialize render target with new size using same memory
|
|
g_viewport_state.width = new_width;
|
|
g_viewport_state.height = new_height;
|
|
|
|
kinc_g4_render_target_t* rt = (kinc_g4_render_target_t*)g_viewport_state.render_target;
|
|
if (rt) {
|
|
kinc_g4_render_target_init(rt, new_width, new_height, KINC_G4_RENDER_TARGET_FORMAT_32BIT, 24, 0);
|
|
}
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
if (header) {
|
|
header->width = new_width;
|
|
header->height = new_height;
|
|
header->ready_flag = 0;
|
|
}
|
|
|
|
kinc_log(KINC_LOG_LEVEL_INFO, "Viewport server resize complete");
|
|
return true;
|
|
}
|
|
|
|
bool viewport_server_check_shutdown(void) {
|
|
if (!viewport_server_is_enabled()) return false;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
return header->shutdown_flag != 0;
|
|
}
|
|
|
|
bool viewport_server_get_view_matrix(float* matrix) {
|
|
if (!viewport_server_is_enabled() || !matrix) return false;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
memcpy(matrix, header->view_matrix, 16 * sizeof(float));
|
|
return true;
|
|
}
|
|
|
|
bool viewport_server_get_proj_matrix(float* matrix) {
|
|
if (!viewport_server_is_enabled() || !matrix) return false;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
memcpy(matrix, header->proj_matrix, 16 * sizeof(float));
|
|
return true;
|
|
}
|
|
|
|
ViewportServerState* viewport_server_get_state(void) {
|
|
return &g_viewport_state;
|
|
}
|
|
|
|
void viewport_server_set_camera(float pos_x, float pos_y, float pos_z,
|
|
float rot_x, float rot_y, float rot_z, float rot_w) {
|
|
if (!viewport_server_is_enabled()) return;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
if (!header) return;
|
|
|
|
header->runt_camera_pos[0] = pos_x;
|
|
header->runt_camera_pos[1] = pos_y;
|
|
header->runt_camera_pos[2] = pos_z;
|
|
|
|
header->runt_camera_rot[0] = rot_x;
|
|
header->runt_camera_rot[1] = rot_y;
|
|
header->runt_camera_rot[2] = rot_z;
|
|
header->runt_camera_rot[3] = rot_w;
|
|
|
|
header->runt_camera_dirty = 1;
|
|
}
|
|
|
|
bool viewport_server_read_input(InputEvent* event) {
|
|
if (!viewport_server_is_enabled()) return false;
|
|
if (!event) return false;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
if (!header) return false;
|
|
|
|
uint32_t read_idx = header->input_read_idx;
|
|
uint32_t write_idx = header->input_write_idx;
|
|
|
|
if (read_idx == write_idx) {
|
|
return false;
|
|
}
|
|
|
|
*event = header->input_events[read_idx % MAX_INPUT_EVENTS];
|
|
|
|
header->input_read_idx = (read_idx + 1) % MAX_INPUT_EVENTS;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool viewport_server_input_enabled(void) {
|
|
if (!viewport_server_is_enabled()) return false;
|
|
|
|
SharedFramebufferHeader* header = (SharedFramebufferHeader*)g_viewport_state.shmem_ptr;
|
|
if (!header) return false;
|
|
|
|
return header->input_enabled != 0;
|
|
}
|