DX12: Initial setup that can render some broken geometry.
This commit is contained in:
parent
c70309c539
commit
b835efea2b
|
|
@ -10,6 +10,21 @@
|
||||||
#include "D3Dcompiler.h"
|
#include "D3Dcompiler.h"
|
||||||
#include <DirectXMath.h>
|
#include <DirectXMath.h>
|
||||||
|
|
||||||
|
const int VERTEX_CHUNK_SIZE = 512 * 1024;
|
||||||
|
|
||||||
|
const int XYZ_SIZE = 4 * VERTEX_CHUNK_SIZE;
|
||||||
|
const int COLOR_SIZE = 1 * VERTEX_CHUNK_SIZE;
|
||||||
|
const int ST0_SIZE = 2 * VERTEX_CHUNK_SIZE;
|
||||||
|
const int ST1_SIZE = 2 * VERTEX_CHUNK_SIZE;
|
||||||
|
|
||||||
|
const int XYZ_OFFSET = 0;
|
||||||
|
const int COLOR_OFFSET = XYZ_OFFSET + XYZ_SIZE;
|
||||||
|
const int ST0_OFFSET = COLOR_OFFSET + COLOR_SIZE;
|
||||||
|
const int ST1_OFFSET = ST0_OFFSET + ST0_SIZE;
|
||||||
|
|
||||||
|
static const int VERTEX_BUFFER_SIZE = XYZ_SIZE + COLOR_SIZE + ST0_SIZE + ST1_SIZE;
|
||||||
|
static const int INDEX_BUFFER_SIZE = 2 * 1024 * 1024;
|
||||||
|
|
||||||
// Helper function for acquiring the first available hardware adapter that supports Direct3D 12.
|
// Helper function for acquiring the first available hardware adapter that supports Direct3D 12.
|
||||||
// If no such adapter can be found, *ppAdapter will be set to nullptr.
|
// If no such adapter can be found, *ppAdapter will be set to nullptr.
|
||||||
static void get_hardware_adapter(IDXGIFactory4* p_factory, IDXGIAdapter1** pp_adapter) {
|
static void get_hardware_adapter(IDXGIFactory4* p_factory, IDXGIAdapter1** pp_adapter) {
|
||||||
|
|
@ -68,11 +83,11 @@ static void wait_for_queue_idle(ID3D12CommandQueue* command_queue) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void record_and_run_commands(ID3D12CommandAllocator* command_allocator, ID3D12CommandQueue* command_queue,
|
static void record_and_run_commands(ID3D12CommandQueue* command_queue, std::function<void(ID3D12GraphicsCommandList*)> recorder) {
|
||||||
std::function<void(ID3D12GraphicsCommandList*)> recorder) {
|
dx.helper_command_allocator->Reset();
|
||||||
|
|
||||||
ID3D12GraphicsCommandList* command_list;
|
ID3D12GraphicsCommandList* command_list;
|
||||||
DX_CHECK(dx.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, command_allocator,
|
DX_CHECK(dx.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, dx.helper_command_allocator,
|
||||||
nullptr, IID_PPV_ARGS(&command_list)));
|
nullptr, IID_PPV_ARGS(&command_list)));
|
||||||
|
|
||||||
recorder(command_list);
|
recorder(command_list);
|
||||||
|
|
@ -168,14 +183,20 @@ void dx_initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
DX_CHECK(dx.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&dx.command_allocator)));
|
DX_CHECK(dx.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&dx.command_allocator)));
|
||||||
|
DX_CHECK(dx.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&dx.helper_command_allocator)));
|
||||||
|
|
||||||
|
//
|
||||||
// Create the root signature.
|
// Create the root signature.
|
||||||
|
//
|
||||||
{
|
{
|
||||||
CD3DX12_DESCRIPTOR_RANGE ranges[1];
|
CD3DX12_DESCRIPTOR_RANGE ranges[2];
|
||||||
ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0);
|
ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
|
||||||
|
ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 1);
|
||||||
|
|
||||||
CD3DX12_ROOT_PARAMETER root_parameters[1];
|
CD3DX12_ROOT_PARAMETER root_parameters[3];
|
||||||
root_parameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_PIXEL);
|
root_parameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_PIXEL);
|
||||||
|
root_parameters[1].InitAsDescriptorTable(1, &ranges[1], D3D12_SHADER_VISIBILITY_PIXEL);
|
||||||
|
root_parameters[2].InitAsConstants(32, 0);
|
||||||
|
|
||||||
D3D12_STATIC_SAMPLER_DESC sampler = {};
|
D3D12_STATIC_SAMPLER_DESC sampler = {};
|
||||||
sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
|
sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
|
||||||
|
|
@ -219,8 +240,8 @@ void dx_initialize() {
|
||||||
// Define the vertex input layout.
|
// Define the vertex input layout.
|
||||||
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
|
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
|
||||||
{
|
{
|
||||||
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
|
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
|
||||||
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
|
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 2, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Describe and create the graphics pipeline state object (PSO).
|
// Describe and create the graphics pipeline state object (PSO).
|
||||||
|
|
@ -230,6 +251,7 @@ void dx_initialize() {
|
||||||
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
|
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
|
||||||
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
|
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
|
||||||
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
|
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
|
||||||
|
|
||||||
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
|
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
|
||||||
psoDesc.DepthStencilState.DepthEnable = FALSE;
|
psoDesc.DepthStencilState.DepthEnable = FALSE;
|
||||||
psoDesc.DepthStencilState.StencilEnable = FALSE;
|
psoDesc.DepthStencilState.StencilEnable = FALSE;
|
||||||
|
|
@ -247,51 +269,6 @@ void dx_initialize() {
|
||||||
// to record yet. The main loop expects it to be closed, so close it now.
|
// to record yet. The main loop expects it to be closed, so close it now.
|
||||||
DX_CHECK(dx.command_list->Close());
|
DX_CHECK(dx.command_list->Close());
|
||||||
|
|
||||||
// Create the vertex buffer.
|
|
||||||
{
|
|
||||||
struct Vertex
|
|
||||||
{
|
|
||||||
XMFLOAT3 position;
|
|
||||||
XMFLOAT2 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
float aspect_ratio = float(glConfig.vidWidth) / float(glConfig.vidHeight);
|
|
||||||
|
|
||||||
// Define the geometry for a triangle.
|
|
||||||
Vertex triangleVertices[] =
|
|
||||||
{
|
|
||||||
{ { 0.0f, 0.5f * aspect_ratio, 0.0f }, { 0.5f, 0.0f } },
|
|
||||||
{ { 0.5f, -0.5f * aspect_ratio, 0.0f }, { 1.0f, 1.0f } },
|
|
||||||
{ { -0.5f, -0.5f * aspect_ratio, 0.0f }, { 0.0f, 1.0f } }
|
|
||||||
};
|
|
||||||
|
|
||||||
const UINT vertexBufferSize = sizeof(triangleVertices);
|
|
||||||
|
|
||||||
// Note: using upload heaps to transfer static data like vert buffers is not
|
|
||||||
// recommended. Every time the GPU needs it, the upload heap will be marshalled
|
|
||||||
// over. Please read up on Default Heap usage. An upload heap is used here for
|
|
||||||
// code simplicity and because there are very few verts to actually transfer.
|
|
||||||
DX_CHECK(dx.device->CreateCommittedResource(
|
|
||||||
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
|
|
||||||
D3D12_HEAP_FLAG_NONE,
|
|
||||||
&CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize),
|
|
||||||
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
||||||
nullptr,
|
|
||||||
IID_PPV_ARGS(&dx.vertex_buffer)));
|
|
||||||
|
|
||||||
// Copy the triangle data to the vertex buffer.
|
|
||||||
UINT8* pVertexDataBegin;
|
|
||||||
CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
|
|
||||||
DX_CHECK(dx.vertex_buffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin)));
|
|
||||||
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
|
|
||||||
dx.vertex_buffer->Unmap(0, nullptr);
|
|
||||||
|
|
||||||
// Initialize the vertex buffer view.
|
|
||||||
dx.vertex_buffer_view.BufferLocation = dx.vertex_buffer->GetGPUVirtualAddress();
|
|
||||||
dx.vertex_buffer_view.StrideInBytes = sizeof(Vertex);
|
|
||||||
dx.vertex_buffer_view.SizeInBytes = vertexBufferSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create synchronization objects.
|
// Create synchronization objects.
|
||||||
{
|
{
|
||||||
DX_CHECK(dx.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&dx.fence)));
|
DX_CHECK(dx.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&dx.fence)));
|
||||||
|
|
@ -310,6 +287,30 @@ void dx_initialize() {
|
||||||
wait_for_previous_frame();
|
wait_for_previous_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Geometry buffers.
|
||||||
|
//
|
||||||
|
{
|
||||||
|
// store geometry in upload heap since Q3 regenerates it every frame
|
||||||
|
DX_CHECK(dx.device->CreateCommittedResource(
|
||||||
|
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
|
||||||
|
D3D12_HEAP_FLAG_NONE,
|
||||||
|
&CD3DX12_RESOURCE_DESC::Buffer(VERTEX_BUFFER_SIZE + INDEX_BUFFER_SIZE),
|
||||||
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||||
|
nullptr,
|
||||||
|
IID_PPV_ARGS(&dx.geometry_buffer)));
|
||||||
|
|
||||||
|
void* p_data;
|
||||||
|
CD3DX12_RANGE read_range(0, 0);
|
||||||
|
DX_CHECK(dx.geometry_buffer->Map(0, &read_range, &p_data));
|
||||||
|
|
||||||
|
dx.vertex_buffer_ptr = static_cast<byte*>(p_data);
|
||||||
|
assert(((size_t)dx.vertex_buffer_ptr & 0xffff) == 0);
|
||||||
|
|
||||||
|
dx.index_buffer_ptr = static_cast<byte*>(p_data) + VERTEX_BUFFER_SIZE;
|
||||||
|
assert(((size_t)dx.index_buffer_ptr & 0xffff) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
dx.active = true;
|
dx.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,6 +320,9 @@ void dx_shutdown() {
|
||||||
dx.command_allocator->Release();
|
dx.command_allocator->Release();
|
||||||
dx.command_allocator = nullptr;
|
dx.command_allocator = nullptr;
|
||||||
|
|
||||||
|
dx.helper_command_allocator->Release();
|
||||||
|
dx.helper_command_allocator = nullptr;
|
||||||
|
|
||||||
for (int i = 0; i < D3D_FRAME_COUNT; i++) {
|
for (int i = 0; i < D3D_FRAME_COUNT; i++) {
|
||||||
dx.render_targets[i]->Release();
|
dx.render_targets[i]->Release();
|
||||||
}
|
}
|
||||||
|
|
@ -347,8 +351,8 @@ void dx_shutdown() {
|
||||||
::CloseHandle(dx.fence_event);
|
::CloseHandle(dx.fence_event);
|
||||||
dx.fence_event = NULL;
|
dx.fence_event = NULL;
|
||||||
|
|
||||||
dx.vertex_buffer->Release();
|
dx.geometry_buffer->Release();
|
||||||
dx.vertex_buffer = nullptr;
|
dx.geometry_buffer = nullptr;
|
||||||
|
|
||||||
dx.device->Release();
|
dx.device->Release();
|
||||||
dx.device = nullptr;
|
dx.device = nullptr;
|
||||||
|
|
@ -361,6 +365,11 @@ void dx_release_resources() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Com_Memset(&dx_world, 0, sizeof(dx_world));
|
Com_Memset(&dx_world, 0, sizeof(dx_world));
|
||||||
|
|
||||||
|
// Reset geometry buffer's current offsets.
|
||||||
|
dx.xyz_elements = 0;
|
||||||
|
dx.color_st_elements = 0;
|
||||||
|
dx.index_buffer_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dx_Image dx_create_image(int width, int height, DXGI_FORMAT format, int mip_levels, bool repeat_texture, int image_index) {
|
Dx_Image dx_create_image(int width, int height, DXGI_FORMAT format, int mip_levels, bool repeat_texture, int image_index) {
|
||||||
|
|
@ -401,6 +410,8 @@ Dx_Image dx_create_image(int width, int height, DXGI_FORMAT format, int mip_leve
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE handle;
|
D3D12_CPU_DESCRIPTOR_HANDLE handle;
|
||||||
handle.ptr = dx.srv_heap->GetCPUDescriptorHandleForHeapStart().ptr + image_index * dx.srv_descriptor_size;
|
handle.ptr = dx.srv_heap->GetCPUDescriptorHandleForHeapStart().ptr + image_index * dx.srv_descriptor_size;
|
||||||
dx.device->CreateShaderResourceView(image.texture, &srv_desc, handle);
|
dx.device->CreateShaderResourceView(image.texture, &srv_desc, handle);
|
||||||
|
|
||||||
|
dx_world.current_image_indices[glState.currenttmu] = image_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
|
|
@ -423,8 +434,7 @@ void dx_upload_image_data(ID3D12Resource* texture, int width, int height, bool m
|
||||||
texture_data.RowPitch = width * bytes_per_pixel;
|
texture_data.RowPitch = width * bytes_per_pixel;
|
||||||
texture_data.SlicePitch = texture_data.RowPitch * height;
|
texture_data.SlicePitch = texture_data.RowPitch * height;
|
||||||
|
|
||||||
record_and_run_commands(dx.command_allocator, dx.command_queue,
|
record_and_run_commands(dx.command_queue, [&texture, &upload_texture, &texture_data](ID3D12GraphicsCommandList* command_list)
|
||||||
[&texture, &upload_texture, &texture_data](ID3D12GraphicsCommandList* command_list)
|
|
||||||
{
|
{
|
||||||
command_list->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(texture,
|
command_list->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(texture,
|
||||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST));
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST));
|
||||||
|
|
@ -436,6 +446,259 @@ void dx_upload_image_data(ID3D12Resource* texture, int width, int height, bool m
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ID3D12PipelineState* dx_find_pipeline(const Vk_Pipeline_Def& def) {
|
||||||
|
return dx.pipeline_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_mvp_transform(float* mvp) {
|
||||||
|
if (backEnd.projection2D) {
|
||||||
|
float mvp0 = 2.0f / glConfig.vidWidth;
|
||||||
|
float mvp5 = 2.0f / glConfig.vidHeight;
|
||||||
|
|
||||||
|
mvp[0] = mvp0; mvp[1] = 0.0f; mvp[2] = 0.0f; mvp[3] = 0.0f;
|
||||||
|
mvp[4] = 0.0f; mvp[5] = -mvp5; mvp[6] = 0.0f; mvp[7] = 0.0f;
|
||||||
|
mvp[8] = 0.0f; mvp[9] = 0.0f; mvp[10] = 1.0f; mvp[11] = 0.0f;
|
||||||
|
mvp[12] = -1.0f; mvp[13] = 1.0f; mvp[14] = 0.0f; mvp[15] = 1.0f;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const float* p = backEnd.viewParms.projectionMatrix;
|
||||||
|
|
||||||
|
// update q3's proj matrix (opengl) to vulkan conventions: z - [0, 1] instead of [-1, 1] and invert y direction
|
||||||
|
float zNear = r_znear->value;
|
||||||
|
float zFar = backEnd.viewParms.zFar;
|
||||||
|
float P10 = -zFar / (zFar - zNear);
|
||||||
|
float P14 = -zFar*zNear / (zFar - zNear);
|
||||||
|
float P5 = -p[5];
|
||||||
|
|
||||||
|
float proj[16] = {
|
||||||
|
p[0], p[1], p[2], p[3],
|
||||||
|
p[4], P5, p[6], p[7],
|
||||||
|
p[8], p[9], P10, p[11],
|
||||||
|
p[12], p[13], P14, p[15]
|
||||||
|
};
|
||||||
|
|
||||||
|
myGlMultMatrix(vk_world.modelview_transform, proj, mvp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dx_bind_geometry() {
|
||||||
|
if (!dx.active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// xyz stream
|
||||||
|
{
|
||||||
|
if ((dx.xyz_elements + tess.numVertexes) * sizeof(vec4_t) > XYZ_SIZE)
|
||||||
|
ri.Error(ERR_DROP, "dx_bind_geometry: vertex buffer overflow (xyz)\n");
|
||||||
|
|
||||||
|
byte* dst = dx.vertex_buffer_ptr + XYZ_OFFSET + dx.xyz_elements * sizeof(vec4_t);
|
||||||
|
Com_Memcpy(dst, tess.xyz, tess.numVertexes * sizeof(vec4_t));
|
||||||
|
|
||||||
|
uint32_t xyz_offset = XYZ_OFFSET + dx.xyz_elements * sizeof(vec4_t);
|
||||||
|
|
||||||
|
D3D12_VERTEX_BUFFER_VIEW xyz_view;
|
||||||
|
xyz_view.BufferLocation = dx.geometry_buffer->GetGPUVirtualAddress() + xyz_offset;
|
||||||
|
xyz_view.SizeInBytes = static_cast<UINT>(tess.numVertexes * sizeof(vec4_t));
|
||||||
|
xyz_view.StrideInBytes = static_cast<UINT>(sizeof(vec4_t));
|
||||||
|
dx.command_list->IASetVertexBuffers(0, 1, &xyz_view);
|
||||||
|
|
||||||
|
dx.xyz_elements += tess.numVertexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexes stream
|
||||||
|
{
|
||||||
|
std::size_t indexes_size = tess.numIndexes * sizeof(uint32_t);
|
||||||
|
|
||||||
|
if (dx.index_buffer_offset + indexes_size > INDEX_BUFFER_SIZE)
|
||||||
|
ri.Error(ERR_DROP, "dx_bind_geometry: index buffer overflow\n");
|
||||||
|
|
||||||
|
byte* dst = dx.index_buffer_ptr + dx.index_buffer_offset;
|
||||||
|
Com_Memcpy(dst, tess.indexes, indexes_size);
|
||||||
|
|
||||||
|
D3D12_INDEX_BUFFER_VIEW index_view;
|
||||||
|
index_view.BufferLocation = dx.geometry_buffer->GetGPUVirtualAddress() + VERTEX_BUFFER_SIZE + dx.index_buffer_offset;
|
||||||
|
index_view.SizeInBytes = static_cast<UINT>(indexes_size);
|
||||||
|
index_view.Format = DXGI_FORMAT_R32_UINT;
|
||||||
|
dx.command_list->IASetIndexBuffer(&index_view);
|
||||||
|
|
||||||
|
dx.index_buffer_offset += static_cast<int>(indexes_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Specify push constants.
|
||||||
|
//
|
||||||
|
float root_constants[16 + 12 + 4]; // mvp transform + eye transform + clipping plane in eye space
|
||||||
|
|
||||||
|
get_mvp_transform(root_constants);
|
||||||
|
int root_constant_count = 16;
|
||||||
|
|
||||||
|
if (backEnd.viewParms.isPortal) {
|
||||||
|
// Eye space transform.
|
||||||
|
// NOTE: backEnd.or.modelMatrix incorporates s_flipMatrix, so it should be taken into account
|
||||||
|
// when computing clipping plane too.
|
||||||
|
float* eye_xform = root_constants + 16;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
eye_xform[i] = backEnd.or.modelMatrix[(i%4)*4 + i/4 ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipping plane in eye coordinates.
|
||||||
|
float world_plane[4];
|
||||||
|
world_plane[0] = backEnd.viewParms.portalPlane.normal[0];
|
||||||
|
world_plane[1] = backEnd.viewParms.portalPlane.normal[1];
|
||||||
|
world_plane[2] = backEnd.viewParms.portalPlane.normal[2];
|
||||||
|
world_plane[3] = backEnd.viewParms.portalPlane.dist;
|
||||||
|
|
||||||
|
float eye_plane[4];
|
||||||
|
eye_plane[0] = DotProduct (backEnd.viewParms.or.axis[0], world_plane);
|
||||||
|
eye_plane[1] = DotProduct (backEnd.viewParms.or.axis[1], world_plane);
|
||||||
|
eye_plane[2] = DotProduct (backEnd.viewParms.or.axis[2], world_plane);
|
||||||
|
eye_plane[3] = DotProduct (world_plane, backEnd.viewParms.or.origin) - world_plane[3];
|
||||||
|
|
||||||
|
// Apply s_flipMatrix to be in the same coordinate system as eye_xfrom.
|
||||||
|
root_constants[28] = -eye_plane[1];
|
||||||
|
root_constants[29] = eye_plane[2];
|
||||||
|
root_constants[30] = -eye_plane[0];
|
||||||
|
root_constants[31] = eye_plane[3];
|
||||||
|
|
||||||
|
root_constant_count += 16;
|
||||||
|
}
|
||||||
|
dx.command_list->SetGraphicsRoot32BitConstants(2, root_constant_count, root_constants, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static D3D12_RECT get_viewport_rect() {
|
||||||
|
D3D12_RECT r;
|
||||||
|
if (backEnd.projection2D) {
|
||||||
|
r.left = 0.0f;
|
||||||
|
r.top = 0.0f;
|
||||||
|
r.right = glConfig.vidWidth;
|
||||||
|
r.bottom = glConfig.vidHeight;
|
||||||
|
} else {
|
||||||
|
r.left = backEnd.viewParms.viewportX;
|
||||||
|
r.top = glConfig.vidHeight - (backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight);
|
||||||
|
r.right = r.left + backEnd.viewParms.viewportWidth;
|
||||||
|
r.bottom = r.top + backEnd.viewParms.viewportHeight;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static D3D12_VIEWPORT get_viewport(Vk_Depth_Range depth_range) {
|
||||||
|
D3D12_RECT r = get_viewport_rect();
|
||||||
|
|
||||||
|
D3D12_VIEWPORT viewport;
|
||||||
|
viewport.TopLeftX = (float)r.left;
|
||||||
|
viewport.TopLeftY = (float)r.top;
|
||||||
|
viewport.Width = (float)(r.right - r.left);
|
||||||
|
viewport.Height = (float)(r.bottom - r.top);
|
||||||
|
|
||||||
|
if (depth_range == Vk_Depth_Range::force_zero) {
|
||||||
|
viewport.MinDepth = 0.0f;
|
||||||
|
viewport.MaxDepth = 0.0f;
|
||||||
|
} else if (depth_range == Vk_Depth_Range::force_one) {
|
||||||
|
viewport.MinDepth = 1.0f;
|
||||||
|
viewport.MaxDepth = 1.0f;
|
||||||
|
} else if (depth_range == Vk_Depth_Range::weapon) {
|
||||||
|
viewport.MinDepth = 0.0f;
|
||||||
|
viewport.MaxDepth = 0.3f;
|
||||||
|
} else {
|
||||||
|
viewport.MinDepth = 0.0f;
|
||||||
|
viewport.MaxDepth = 1.0f;
|
||||||
|
}
|
||||||
|
return viewport;
|
||||||
|
}
|
||||||
|
|
||||||
|
static D3D12_RECT get_scissor_rect() {
|
||||||
|
D3D12_RECT r = get_viewport_rect();
|
||||||
|
|
||||||
|
if (r.left < 0)
|
||||||
|
r.left = 0;
|
||||||
|
if (r.top < 0)
|
||||||
|
r.top = 0;
|
||||||
|
|
||||||
|
if (r.right > glConfig.vidWidth)
|
||||||
|
r.right = glConfig.vidWidth;
|
||||||
|
if (r.bottom > glConfig.vidHeight)
|
||||||
|
r.bottom = glConfig.vidHeight;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dx_shade_geometry(ID3D12PipelineState* pipeline_state, bool multitexture, Vk_Depth_Range depth_range, bool indexed) {
|
||||||
|
// color
|
||||||
|
{
|
||||||
|
if ((dx.color_st_elements + tess.numVertexes) * sizeof(color4ub_t) > COLOR_SIZE)
|
||||||
|
ri.Error(ERR_DROP, "vulkan: vertex buffer overflow (color)\n");
|
||||||
|
|
||||||
|
byte* dst = dx.vertex_buffer_ptr + COLOR_OFFSET + dx.color_st_elements * sizeof(color4ub_t);
|
||||||
|
Com_Memcpy(dst, tess.svars.colors, tess.numVertexes * sizeof(color4ub_t));
|
||||||
|
}
|
||||||
|
// st0
|
||||||
|
{
|
||||||
|
if ((dx.color_st_elements + tess.numVertexes) * sizeof(vec2_t) > ST0_SIZE)
|
||||||
|
ri.Error(ERR_DROP, "vulkan: vertex buffer overflow (st0)\n");
|
||||||
|
|
||||||
|
byte* dst = dx.vertex_buffer_ptr + ST0_OFFSET + dx.color_st_elements * sizeof(vec2_t);
|
||||||
|
Com_Memcpy(dst, tess.svars.texcoords[0], tess.numVertexes * sizeof(vec2_t));
|
||||||
|
}
|
||||||
|
// st1
|
||||||
|
if (multitexture) {
|
||||||
|
if ((dx.color_st_elements + tess.numVertexes) * sizeof(vec2_t) > ST1_SIZE)
|
||||||
|
ri.Error(ERR_DROP, "vulkan: vertex buffer overflow (st1)\n");
|
||||||
|
|
||||||
|
byte* dst = dx.vertex_buffer_ptr + ST1_OFFSET + dx.color_st_elements * sizeof(vec2_t);
|
||||||
|
Com_Memcpy(dst, tess.svars.texcoords[1], tess.numVertexes * sizeof(vec2_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure vertex data stream
|
||||||
|
D3D12_VERTEX_BUFFER_VIEW color_st_views[3];
|
||||||
|
color_st_views[0].BufferLocation = dx.geometry_buffer->GetGPUVirtualAddress() + COLOR_OFFSET + dx.color_st_elements * sizeof(color4ub_t);
|
||||||
|
color_st_views[0].SizeInBytes = static_cast<UINT>(tess.numVertexes * sizeof(color4ub_t));
|
||||||
|
color_st_views[0].StrideInBytes = static_cast<UINT>(sizeof(color4ub_t));
|
||||||
|
|
||||||
|
color_st_views[1].BufferLocation = dx.geometry_buffer->GetGPUVirtualAddress() + ST0_OFFSET + dx.color_st_elements * sizeof(vec2_t);
|
||||||
|
color_st_views[1].SizeInBytes = static_cast<UINT>(tess.numVertexes * sizeof(vec2_t));
|
||||||
|
color_st_views[1].StrideInBytes = static_cast<UINT>(sizeof(vec2_t));
|
||||||
|
|
||||||
|
color_st_views[2].BufferLocation = dx.geometry_buffer->GetGPUVirtualAddress() + ST1_OFFSET + dx.color_st_elements * sizeof(vec2_t);
|
||||||
|
color_st_views[2].SizeInBytes = static_cast<UINT>(tess.numVertexes * sizeof(vec2_t));
|
||||||
|
color_st_views[2].StrideInBytes = static_cast<UINT>(sizeof(vec2_t));
|
||||||
|
|
||||||
|
dx.command_list->IASetVertexBuffers(1, multitexture ? 3 : 2, color_st_views);
|
||||||
|
|
||||||
|
dx.color_st_elements += tess.numVertexes;
|
||||||
|
|
||||||
|
// bind descriptor sets
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE handle = dx.srv_heap->GetGPUDescriptorHandleForHeapStart();
|
||||||
|
handle.ptr += dx.srv_descriptor_size * dx_world.current_image_indices[0];
|
||||||
|
dx.command_list->SetGraphicsRootDescriptorTable(0, handle);
|
||||||
|
|
||||||
|
if (multitexture) {
|
||||||
|
handle = dx.srv_heap->GetGPUDescriptorHandleForHeapStart();
|
||||||
|
handle.ptr += dx.srv_descriptor_size * dx_world.current_image_indices[1];
|
||||||
|
dx.command_list->SetGraphicsRootDescriptorTable(1, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind pipeline
|
||||||
|
dx.command_list->SetPipelineState(pipeline_state);
|
||||||
|
|
||||||
|
// configure pipeline's dynamic state
|
||||||
|
D3D12_RECT scissor_rect = get_scissor_rect();
|
||||||
|
dx.command_list->RSSetScissorRects(1, &scissor_rect);
|
||||||
|
|
||||||
|
D3D12_VIEWPORT viewport = get_viewport(depth_range);
|
||||||
|
dx.command_list->RSSetViewports(1, &viewport);
|
||||||
|
|
||||||
|
/*if (tess.shader->polygonOffset) {
|
||||||
|
vkCmdSetDepthBias(vk.command_buffer, r_offsetUnits->value, 0.0f, r_offsetFactor->value);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// issue draw call
|
||||||
|
if (indexed)
|
||||||
|
dx.command_list->DrawIndexedInstanced(tess.numIndexes, 1, 0, 0, 0);
|
||||||
|
else
|
||||||
|
dx.command_list->DrawInstanced(tess.numVertexes, 1, 0, 0);
|
||||||
|
|
||||||
|
vk_world.dirty_depth_attachment = true;
|
||||||
|
}
|
||||||
|
|
||||||
void dx_begin_frame() {
|
void dx_begin_frame() {
|
||||||
// Command list allocators can only be reset when the associated
|
// Command list allocators can only be reset when the associated
|
||||||
// command lists have finished execution on the GPU; apps should use
|
// command lists have finished execution on the GPU; apps should use
|
||||||
|
|
@ -475,17 +738,18 @@ void dx_begin_frame() {
|
||||||
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
|
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
|
||||||
dx.command_list->ClearRenderTargetView(rtv_handle, clearColor, 0, nullptr);
|
dx.command_list->ClearRenderTargetView(rtv_handle, clearColor, 0, nullptr);
|
||||||
dx.command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
dx.command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||||
dx.command_list->IASetVertexBuffers(0, 1, &dx.vertex_buffer_view);
|
|
||||||
dx.command_list->DrawInstanced(3, 1, 0, 0);
|
|
||||||
|
|
||||||
// Indicate that the back buffer will now be used to present.
|
dx.xyz_elements = 0;
|
||||||
|
dx.color_st_elements = 0;
|
||||||
|
dx.index_buffer_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dx_end_frame() {
|
||||||
dx.command_list->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(dx.render_targets[dx.frame_index],
|
dx.command_list->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(dx.render_targets[dx.frame_index],
|
||||||
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
|
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
|
||||||
|
|
||||||
DX_CHECK(dx.command_list->Close());
|
DX_CHECK(dx.command_list->Close());
|
||||||
}
|
|
||||||
|
|
||||||
void dx_end_frame() {
|
|
||||||
// Execute the command list.
|
// Execute the command list.
|
||||||
ID3D12CommandList* ppCommandLists[] = { dx.command_list };
|
ID3D12CommandList* ppCommandLists[] = { dx.command_list };
|
||||||
dx.command_queue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
|
dx.command_queue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,13 @@ void dx_release_resources();
|
||||||
//
|
//
|
||||||
Dx_Image dx_create_image(int width, int height, DXGI_FORMAT format, int mip_levels, bool repeat_texture, int image_index);
|
Dx_Image dx_create_image(int width, int height, DXGI_FORMAT format, int mip_levels, bool repeat_texture, int image_index);
|
||||||
void dx_upload_image_data(ID3D12Resource* texture, int width, int height, bool mipmap, const uint8_t* pixels, int bytes_per_pixel);
|
void dx_upload_image_data(ID3D12Resource* texture, int width, int height, bool mipmap, const uint8_t* pixels, int bytes_per_pixel);
|
||||||
|
ID3D12PipelineState* dx_find_pipeline(const Vk_Pipeline_Def& def);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Rendering setup.
|
// Rendering setup.
|
||||||
//
|
//
|
||||||
|
void dx_bind_geometry();
|
||||||
|
void dx_shade_geometry(ID3D12PipelineState* pipeline_state, bool multitexture, Vk_Depth_Range depth_range, bool indexed = true);
|
||||||
void dx_begin_frame();
|
void dx_begin_frame();
|
||||||
void dx_end_frame();
|
void dx_end_frame();
|
||||||
|
|
||||||
|
|
@ -55,6 +58,7 @@ struct Dx_Instance {
|
||||||
ID3D12Resource* render_targets[D3D_FRAME_COUNT];
|
ID3D12Resource* render_targets[D3D_FRAME_COUNT];
|
||||||
ID3D12RootSignature* root_signature = nullptr;
|
ID3D12RootSignature* root_signature = nullptr;
|
||||||
ID3D12CommandAllocator* command_allocator = nullptr;
|
ID3D12CommandAllocator* command_allocator = nullptr;
|
||||||
|
ID3D12CommandAllocator* helper_command_allocator = nullptr;
|
||||||
ID3D12GraphicsCommandList* command_list = nullptr;
|
ID3D12GraphicsCommandList* command_list = nullptr;
|
||||||
ID3D12PipelineState* pipeline_state = nullptr;
|
ID3D12PipelineState* pipeline_state = nullptr;
|
||||||
|
|
||||||
|
|
@ -63,11 +67,17 @@ struct Dx_Instance {
|
||||||
UINT64 fence_value = 0;
|
UINT64 fence_value = 0;
|
||||||
HANDLE fence_event = NULL;
|
HANDLE fence_event = NULL;
|
||||||
|
|
||||||
ID3D12Resource* vertex_buffer = nullptr;
|
|
||||||
D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view;
|
|
||||||
|
|
||||||
ID3D12DescriptorHeap* srv_heap = nullptr;
|
ID3D12DescriptorHeap* srv_heap = nullptr;
|
||||||
UINT srv_descriptor_size = 0;
|
UINT srv_descriptor_size = 0;
|
||||||
|
|
||||||
|
byte* vertex_buffer_ptr = nullptr; // pointer to mapped vertex buffer
|
||||||
|
int xyz_elements = 0;
|
||||||
|
int color_st_elements = 0;
|
||||||
|
|
||||||
|
byte* index_buffer_ptr = nullptr; // pointer to mapped index buffer
|
||||||
|
int index_buffer_offset = 0;
|
||||||
|
|
||||||
|
ID3D12Resource* geometry_buffer = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Dx_World {
|
struct Dx_World {
|
||||||
|
|
@ -75,4 +85,9 @@ struct Dx_World {
|
||||||
// Resources.
|
// Resources.
|
||||||
//
|
//
|
||||||
Dx_Image images[MAX_VK_IMAGES];
|
Dx_Image images[MAX_VK_IMAGES];
|
||||||
|
|
||||||
|
//
|
||||||
|
// State.
|
||||||
|
//
|
||||||
|
int current_image_indices[2];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@ struct PSInput
|
||||||
float2 uv : TEXCOORD;
|
float2 uv : TEXCOORD;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cbuffer Constants : register(b0)
|
||||||
|
{
|
||||||
|
float4x4 clip_space_xform;
|
||||||
|
};
|
||||||
|
|
||||||
Texture2D texture0 : register(t0);
|
Texture2D texture0 : register(t0);
|
||||||
SamplerState sampler0 : register(s0);
|
SamplerState sampler0 : register(s0);
|
||||||
|
|
||||||
|
|
@ -11,7 +16,7 @@ PSInput VSMain(float4 position : POSITION, float2 uv : TEXCOORD)
|
||||||
{
|
{
|
||||||
PSInput result;
|
PSInput result;
|
||||||
|
|
||||||
result.position = position;
|
result.position = mul(clip_space_xform, position);
|
||||||
result.uv = uv;
|
result.uv = uv;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ void GL_Bind( image_t *image ) {
|
||||||
VkDescriptorSet set = vk_world.images[final_image->index].descriptor_set;
|
VkDescriptorSet set = vk_world.images[final_image->index].descriptor_set;
|
||||||
vk_world.current_descriptor_sets[glState.currenttmu] = set;
|
vk_world.current_descriptor_sets[glState.currenttmu] = set;
|
||||||
}
|
}
|
||||||
|
// D3D
|
||||||
|
if (dx.active) {
|
||||||
|
dx_world.current_image_indices[glState.currenttmu] = final_image->index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,12 @@ typedef struct {
|
||||||
VkPipeline vk_pipeline = VK_NULL_HANDLE;
|
VkPipeline vk_pipeline = VK_NULL_HANDLE;
|
||||||
VkPipeline vk_portal_pipeline = VK_NULL_HANDLE;
|
VkPipeline vk_portal_pipeline = VK_NULL_HANDLE;
|
||||||
VkPipeline vk_mirror_pipeline = VK_NULL_HANDLE;
|
VkPipeline vk_mirror_pipeline = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
// D3D
|
||||||
|
ID3D12PipelineState* dx_pipeline_state = nullptr;
|
||||||
|
ID3D12PipelineState* dx_portal_pipeline_state = nullptr;
|
||||||
|
ID3D12PipelineState* dx_mirror_pipeline_state = nullptr;
|
||||||
|
|
||||||
} shaderStage_t;
|
} shaderStage_t;
|
||||||
|
|
||||||
struct shaderCommands_s;
|
struct shaderCommands_s;
|
||||||
|
|
|
||||||
|
|
@ -748,6 +748,9 @@ static void RB_IterateStagesGeneric( shaderCommands_t *input )
|
||||||
// VULKAN
|
// VULKAN
|
||||||
vk_bind_geometry();
|
vk_bind_geometry();
|
||||||
|
|
||||||
|
// DX12
|
||||||
|
dx_bind_geometry();
|
||||||
|
|
||||||
for ( int stage = 0; stage < MAX_SHADER_STAGES; stage++ )
|
for ( int stage = 0; stage < MAX_SHADER_STAGES; stage++ )
|
||||||
{
|
{
|
||||||
shaderStage_t *pStage = tess.xstages[stage];
|
shaderStage_t *pStage = tess.xstages[stage];
|
||||||
|
|
@ -817,6 +820,26 @@ static void RB_IterateStagesGeneric( shaderCommands_t *input )
|
||||||
vk_shade_geometry(pipeline, multitexture, depth_range);
|
vk_shade_geometry(pipeline, multitexture, depth_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DX12
|
||||||
|
if (dx.active) {
|
||||||
|
ID3D12PipelineState* pipeline_state = pStage->dx_pipeline_state;
|
||||||
|
if (backEnd.viewParms.isMirror)
|
||||||
|
pipeline_state = pStage->dx_mirror_pipeline_state;
|
||||||
|
else if (backEnd.viewParms.isPortal)
|
||||||
|
pipeline_state = pStage->dx_portal_pipeline_state;
|
||||||
|
|
||||||
|
Vk_Depth_Range depth_range = Vk_Depth_Range::normal;
|
||||||
|
if (input->shader->isSky) {
|
||||||
|
depth_range = Vk_Depth_Range::force_one;
|
||||||
|
if (r_showsky->integer)
|
||||||
|
depth_range = Vk_Depth_Range::force_zero;
|
||||||
|
} else if (backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) {
|
||||||
|
depth_range = Vk_Depth_Range::weapon;
|
||||||
|
}
|
||||||
|
|
||||||
|
dx_shade_geometry(pipeline_state, multitexture, depth_range);
|
||||||
|
}
|
||||||
|
|
||||||
// allow skipping out to show just lightmaps during development
|
// allow skipping out to show just lightmaps during development
|
||||||
if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap ) )
|
if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap ) )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2161,7 +2161,8 @@ static shader_t *FinishShader( void ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VULKAN: create pipelines for each shader stage
|
// VULKAN: create pipelines for each shader stage
|
||||||
if (vk.active) {
|
// DX12
|
||||||
|
if (vk.active || dx.active) {
|
||||||
Vk_Pipeline_Def def;
|
Vk_Pipeline_Def def;
|
||||||
def.face_culling = shader.cullType;
|
def.face_culling = shader.cullType;
|
||||||
def.polygon_offset = (shader.polygonOffset == qtrue);
|
def.polygon_offset = (shader.polygonOffset == qtrue);
|
||||||
|
|
@ -2181,15 +2182,24 @@ static shader_t *FinishShader( void ) {
|
||||||
|
|
||||||
def.clipping_plane = false;
|
def.clipping_plane = false;
|
||||||
def.mirror = false;
|
def.mirror = false;
|
||||||
|
if (vk.active)
|
||||||
pStage->vk_pipeline = vk_find_pipeline(def);
|
pStage->vk_pipeline = vk_find_pipeline(def);
|
||||||
|
if (dx.active)
|
||||||
|
pStage->dx_pipeline_state = dx_find_pipeline(def);
|
||||||
|
|
||||||
def.clipping_plane = true;
|
def.clipping_plane = true;
|
||||||
def.mirror = false;
|
def.mirror = false;
|
||||||
|
if (vk.active)
|
||||||
pStage->vk_portal_pipeline = vk_find_pipeline(def);
|
pStage->vk_portal_pipeline = vk_find_pipeline(def);
|
||||||
|
if (dx.active)
|
||||||
|
pStage->dx_portal_pipeline_state = dx_find_pipeline(def);
|
||||||
|
|
||||||
def.clipping_plane = true;
|
def.clipping_plane = true;
|
||||||
def.mirror = true;
|
def.mirror = true;
|
||||||
|
if (vk.active)
|
||||||
pStage->vk_mirror_pipeline = vk_find_pipeline(def);
|
pStage->vk_mirror_pipeline = vk_find_pipeline(def);
|
||||||
|
if (dx.active)
|
||||||
|
pStage->dx_mirror_pipeline_state = dx_find_pipeline(def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user