diff --git a/Makefile.am b/Makefile.am index 2b7a05cb9..371c340f6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -325,6 +325,7 @@ vkd3d_test_headers = \ vkd3d_demos = \ demos/vkd3d-gears \ + demos/vkd3d-teapot \ demos/vkd3d-triangle vkd3d_demos_headers = \ @@ -549,6 +550,14 @@ demos_vkd3d_gears_CFLAGS = $(DEMOS_CFLAGS) demos_vkd3d_gears_LDADD = $(DEMOS_LDADD) -lm demos/vkd3d_gears-gears.$(OBJEXT): demos/gears.hlsl +demos_vkd3d_teapot_SOURCES = \ + demos/teapot.c \ + demos/teapot.h \ + demos/teapot.hlsl +demos_vkd3d_teapot_CFLAGS = $(DEMOS_CFLAGS) +demos_vkd3d_teapot_LDADD = $(DEMOS_LDADD) -lm +demos/vkd3d_teapot-teapot.$(OBJEXT): demos/teapot.hlsl + demos_vkd3d_triangle_SOURCES = demos/triangle.c demos/triangle.hlsl demos_vkd3d_triangle_CFLAGS = $(DEMOS_CFLAGS) demos_vkd3d_triangle_LDADD = $(DEMOS_LDADD) @@ -657,6 +666,7 @@ endif if BUILD_DEMOS CROSS32_EXEFILES += $(vkd3d_demos:demos/vkd3d-%=demos/%.cross32.exe) demos/gears.cross32.exe: demos/gears.hlsl +demos/teapot.cross32.exe: demos/teapot.hlsl demos/triangle.cross32.exe: demos/triangle.hlsl endif @@ -706,6 +716,7 @@ endif if BUILD_DEMOS CROSS64_EXEFILES += $(vkd3d_demos:demos/vkd3d-%=demos/%.cross64.exe) demos/gears.cross64.exe: demos/gears.hlsl +demos/teapot.cross64.exe: demos/teapot.hlsl demos/triangle.cross64.exe: demos/triangle.hlsl endif diff --git a/demos/demo.h b/demos/demo.h index 765a3c0e3..f8d20d399 100644 --- a/demos/demo.h +++ b/demos/demo.h @@ -38,6 +38,7 @@ #define COBJMACROS #include #include +#include #ifdef __WIN32__ #define DEMO_ASM_PUSHSECTION ".section rdata\n\t" @@ -81,12 +82,14 @@ #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) -#define DEMO_KEY_UNKNOWN 0x0000 -#define DEMO_KEY_ESCAPE 0xff1b -#define DEMO_KEY_LEFT 0xff51 -#define DEMO_KEY_UP 0xff52 -#define DEMO_KEY_RIGHT 0xff53 -#define DEMO_KEY_DOWN 0xff54 +#define DEMO_KEY_UNKNOWN 0x0000 +#define DEMO_KEY_ESCAPE 0xff1b +#define DEMO_KEY_LEFT 0xff51 +#define DEMO_KEY_UP 0xff52 +#define DEMO_KEY_RIGHT 0xff53 +#define DEMO_KEY_DOWN 0xff54 +#define DEMO_KEY_KP_ADD 0xffab +#define DEMO_KEY_KP_SUBTRACT 0xffad struct demo_vec3 { @@ -103,6 +106,11 @@ struct demo_matrix float m[4][4]; }; +struct demo_patch +{ + uint16_t p[4][4]; +}; + struct demo_swapchain_desc { unsigned int width; @@ -120,6 +128,36 @@ static inline void demo_vec3_set(struct demo_vec3 *v, float x, float y, float z) v->z = z; } +static inline void demo_vec3_subtract(struct demo_vec3 *v, const struct demo_vec3 *a, const struct demo_vec3 *b) +{ + demo_vec3_set(v, a->x - b->x, a->y - b->y, a->z - b->z); +} + +static inline float demo_vec3_dot(const struct demo_vec3 *a, const struct demo_vec3 *b) +{ + return a->x * b->x + a->y * b->y + a->z * b->z; +} + +static inline float demo_vec3_length(const struct demo_vec3 *v) +{ + return sqrtf(demo_vec3_dot(v, v)); +} + +static inline void demo_vec3_scale(struct demo_vec3 *v, const struct demo_vec3 *a, float s) +{ + demo_vec3_set(v, a->x * s, a->y * s, a->z *s); +} + +static inline void demo_vec3_normalise(struct demo_vec3 *v, const struct demo_vec3 *a) +{ + demo_vec3_scale(v, a, 1.0f / demo_vec3_length(a)); +} + +static inline void demo_vec3_cross(struct demo_vec3 *v, const struct demo_vec3 *a, const struct demo_vec3 *b) +{ + demo_vec3_set(v, a->y * b->z - a->z * b->y, a->z * b->x - a->x * b->z, a->x * b->y - a->y * b->x); +} + static inline void demo_vec4_set(struct demo_vec4 *v, float x, float y, float z, float w) { v->x = x; @@ -128,6 +166,27 @@ static inline void demo_vec4_set(struct demo_vec4 *v, float x, float y, float z, v->w = w; } +static inline void demo_matrix_look_at_rh(struct demo_matrix *m, const struct demo_vec3 *eye, + const struct demo_vec3 *ref, const struct demo_vec3 *up) +{ + struct demo_vec3 f, s, t, u; + + demo_vec3_subtract(&f, eye, ref); + demo_vec3_normalise(&f, &f); + demo_vec3_cross(&s, up, &f); + demo_vec3_normalise(&s, &s); + demo_vec3_cross(&u, &f, &s); + demo_vec3_set(&t, demo_vec3_dot(&s, eye), demo_vec3_dot(&u, eye), demo_vec3_dot(&f, eye)); + + *m = (struct demo_matrix) + {{ + { s.x, u.x, f.x, 0.0f}, + { s.y, u.y, f.y, 0.0f}, + { s.z, u.z, f.z, 0.0f}, + {-t.x, -t.y, -t.z, 1.0f}, + }}; +} + static inline void demo_matrix_multiply(struct demo_matrix *out, const struct demo_matrix *a, const struct demo_matrix *b) { diff --git a/demos/demo_macos.h b/demos/demo_macos.h index cdafb9a66..5ac824f97 100644 --- a/demos/demo_macos.h +++ b/demos/demo_macos.h @@ -176,12 +176,16 @@ static demo_key demo_key_from_nsevent(id event) { enum vkey { - kVK_ANSI_A = 0x00, - kVK_Escape = 0x35, - kVK_LeftArrow = 0x7b, - kVK_RightArrow = 0x7c, - kVK_DownArrow = 0x7d, - kVK_UpArrow = 0x7e, + kVK_ANSI_A = 0x00, + kVK_ANSI_Equal = 0x18, + kVK_ANSI_Minus = 0x1b, + kVK_Escape = 0x35, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadMinus = 0x4e, + kVK_LeftArrow = 0x7b, + kVK_RightArrow = 0x7c, + kVK_DownArrow = 0x7d, + kVK_UpArrow = 0x7e, } vkey; size_t i; @@ -192,12 +196,16 @@ static demo_key demo_key_from_nsevent(id event) } lookup[] = { - {kVK_ANSI_A, 'a'}, - {kVK_Escape, DEMO_KEY_ESCAPE}, - {kVK_LeftArrow, DEMO_KEY_LEFT}, - {kVK_RightArrow, DEMO_KEY_RIGHT}, - {kVK_UpArrow, DEMO_KEY_UP}, - {kVK_DownArrow, DEMO_KEY_DOWN}, + {kVK_ANSI_A, 'a'}, + {kVK_ANSI_Equal, '='}, + {kVK_ANSI_Minus, '-'}, + {kVK_Escape, DEMO_KEY_ESCAPE}, + {kVK_ANSI_KeypadPlus, DEMO_KEY_KP_ADD}, + {kVK_ANSI_KeypadMinus, DEMO_KEY_KP_SUBTRACT}, + {kVK_LeftArrow, DEMO_KEY_LEFT}, + {kVK_RightArrow, DEMO_KEY_RIGHT}, + {kVK_DownArrow, DEMO_KEY_DOWN}, + {kVK_UpArrow, DEMO_KEY_UP}, }; vkey = NSEvent_keyCode(event); diff --git a/demos/demo_win32.h b/demos/demo_win32.h index 28e512151..03bcaad9f 100644 --- a/demos/demo_win32.h +++ b/demos/demo_win32.h @@ -127,11 +127,15 @@ static demo_key demo_key_from_win32_vkey(DWORD vkey) } lookup[] = { - {VK_ESCAPE, DEMO_KEY_ESCAPE}, - {VK_LEFT, DEMO_KEY_LEFT}, - {VK_RIGHT, DEMO_KEY_RIGHT}, - {VK_UP, DEMO_KEY_UP}, - {VK_DOWN, DEMO_KEY_DOWN}, + {VK_OEM_MINUS, '-'}, + {VK_OEM_PLUS, '='}, + {VK_ESCAPE, DEMO_KEY_ESCAPE}, + {VK_LEFT, DEMO_KEY_LEFT}, + {VK_UP, DEMO_KEY_UP}, + {VK_RIGHT, DEMO_KEY_RIGHT}, + {VK_DOWN, DEMO_KEY_DOWN}, + {VK_ADD, DEMO_KEY_KP_ADD}, + {VK_SUBTRACT, DEMO_KEY_KP_SUBTRACT}, }; unsigned int i; diff --git a/demos/gears.c b/demos/gears.c index f7f82f4c9..0d556e81e 100644 --- a/demos/gears.c +++ b/demos/gears.c @@ -45,7 +45,6 @@ #include #include #include -#include #include "demo.h" DEMO_EMBED(gears_hlsl, "gears.hlsl"); diff --git a/demos/teapot.c b/demos/teapot.c new file mode 100644 index 000000000..2a72297d2 --- /dev/null +++ b/demos/teapot.c @@ -0,0 +1,577 @@ +/* + * Copyright 2025 Henri Verbeet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define INITGUID +#define _GNU_SOURCE +#include +#include +#include +#include "demo.h" + +#include "teapot.h" + +DEMO_EMBED(teapot_hlsl, "teapot.hlsl"); + +struct teapot_fence +{ + ID3D12Fence *fence; + UINT64 value; +}; + +struct teapot_cb_data +{ + struct demo_matrix mvp_matrix; + float level; +}; + +struct teapot +{ + struct demo demo; + + struct demo_window *window; + + unsigned int width, height; + unsigned int tessellation_level; + float theta, phi; + + D3D12_VIEWPORT vp; + D3D12_RECT scissor_rect; + + ID3D12Device *device; + ID3D12CommandQueue *command_queue; + struct demo_swapchain *swapchain; + struct + { + ID3D12Resource *render_target; + ID3D12CommandAllocator *command_allocator; + ID3D12GraphicsCommandList *command_list; + } *swapchain_images; + ID3D12DescriptorHeap *rtv_heap; + unsigned int rtv_descriptor_size; + + ID3D12RootSignature *root_signature; + ID3D12PipelineState *pipeline_state; + ID3D12Resource *cb, *vb, *ib; + D3D12_VERTEX_BUFFER_VIEW vbv; + D3D12_INDEX_BUFFER_VIEW ibv; + + unsigned int rt_idx; + struct teapot_fence fence; + + struct teapot_cb_data *cb_data; +}; + +static void teapot_populate_command_list(struct teapot *teapot, + ID3D12GraphicsCommandList *command_list, unsigned int rt_idx) +{ + size_t rotate_idx_count, flip_idx_count; + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle; + D3D12_RESOURCE_BARRIER barrier; + HRESULT hr; + + hr = ID3D12GraphicsCommandList_Reset(command_list, + teapot->swapchain_images[rt_idx].command_allocator, teapot->pipeline_state); + assert(SUCCEEDED(hr)); + + ID3D12GraphicsCommandList_SetGraphicsRootSignature(command_list, teapot->root_signature); + ID3D12GraphicsCommandList_SetGraphicsRootConstantBufferView(command_list, 0, + ID3D12Resource_GetGPUVirtualAddress(teapot->cb)); + + ID3D12GraphicsCommandList_RSSetViewports(command_list, 1, &teapot->vp); + ID3D12GraphicsCommandList_RSSetScissorRects(command_list, 1, &teapot->scissor_rect); + + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = teapot->swapchain_images[rt_idx].render_target; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + ID3D12GraphicsCommandList_ResourceBarrier(command_list, 1, &barrier); + + rtv_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(teapot->rtv_heap); + rtv_handle.ptr += rt_idx * teapot->rtv_descriptor_size; + ID3D12GraphicsCommandList_OMSetRenderTargets(command_list, 1, &rtv_handle, FALSE, NULL); + + ID3D12GraphicsCommandList_ClearRenderTargetView(command_list, rtv_handle, + (float[]){1.00f * 0.1f, 0.69f * 0.1f, 0.00f, 1.0f}, 0, NULL); + ID3D12GraphicsCommandList_IASetPrimitiveTopology(command_list, D3D_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST); + ID3D12GraphicsCommandList_IASetIndexBuffer(command_list, &teapot->ibv); + ID3D12GraphicsCommandList_IASetVertexBuffers(command_list, 0, 1, &teapot->vbv); + + rotate_idx_count = ARRAY_SIZE(teapot_rotate_patches) * 16; + ID3D12GraphicsCommandList_DrawIndexedInstanced(command_list, rotate_idx_count, 4, 0, 0, 0); + flip_idx_count = ARRAY_SIZE(teapot_flip_patches) * 16; + ID3D12GraphicsCommandList_DrawIndexedInstanced(command_list, flip_idx_count, 2, rotate_idx_count, 0, 0); + + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + ID3D12GraphicsCommandList_ResourceBarrier(command_list, 1, &barrier); + + hr = ID3D12GraphicsCommandList_Close(command_list); + assert(SUCCEEDED(hr)); +} + +static void teapot_populate_command_lists(struct teapot *teapot) +{ + HRESULT hr; + size_t i; + + for (i = 0; i < demo_swapchain_get_back_buffer_count(teapot->swapchain); ++i) + { + hr = ID3D12CommandAllocator_Reset(teapot->swapchain_images[i].command_allocator); + assert(SUCCEEDED(hr)); + teapot_populate_command_list(teapot, teapot->swapchain_images[i].command_list, i); + } +} + +static void teapot_wait_for_previous_frame(struct teapot *teapot) +{ + struct teapot_fence *fence = &teapot->fence; + const UINT64 v = fence->value++; + HRESULT hr; + + hr = ID3D12CommandQueue_Signal(teapot->command_queue, fence->fence, v); + assert(SUCCEEDED(hr)); + hr = ID3D12Fence_SetEventOnCompletion(fence->fence, v, NULL); + assert(SUCCEEDED(hr)); + + teapot->rt_idx = demo_swapchain_get_current_back_buffer_index(teapot->swapchain); +} + +static void teapot_update_mvp(struct teapot *teapot) +{ + struct demo_vec3 up = {0.0f, 0.0f, teapot->theta < 0.0f ? -1.0f : 1.0f}; + struct demo_vec3 ref = {0.0f, 0.0f, 1.5f}; + struct demo_matrix projection, world; + struct demo_vec3 eye; + float r = 25.0f; + + eye.z = 1.5 + r * cosf(teapot->theta); + r *= sinf(teapot->theta); + eye.y = r * sinf(teapot->phi); + eye.x = r * cosf(teapot->phi); + + demo_matrix_perspective_rh(&projection, 2.0f, 2.0f * teapot->height / teapot->width, 5.0f, 160.0f); + demo_matrix_look_at_rh(&world, &eye, &ref, &up); + demo_matrix_multiply(&teapot->cb_data->mvp_matrix, &world, &projection); +} + +static void teapot_render_frame(struct teapot *teapot) +{ + ID3D12CommandQueue_ExecuteCommandLists(teapot->command_queue, 1, + (ID3D12CommandList **)&teapot->swapchain_images[teapot->rt_idx].command_list); + demo_swapchain_present(teapot->swapchain); + teapot_wait_for_previous_frame(teapot); +} + +static void teapot_destroy_pipeline(struct teapot *teapot) +{ + unsigned int i; + + ID3D12DescriptorHeap_Release(teapot->rtv_heap); + for (i = 0; i < demo_swapchain_get_back_buffer_count(teapot->swapchain); ++i) + { + ID3D12CommandAllocator_Release(teapot->swapchain_images[i].command_allocator); + ID3D12Resource_Release(teapot->swapchain_images[i].render_target); + } + free(teapot->swapchain_images); + demo_swapchain_destroy(teapot->swapchain); + ID3D12CommandQueue_Release(teapot->command_queue); + ID3D12Device_Release(teapot->device); +} + +static void teapot_load_pipeline(struct teapot *teapot) +{ + struct demo_swapchain_desc swapchain_desc; + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle; + D3D12_DESCRIPTOR_HEAP_DESC heap_desc; + D3D12_COMMAND_QUEUE_DESC queue_desc; + unsigned int i, rt_count; + HRESULT hr; + + hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12Device, (void **)&teapot->device); + assert(SUCCEEDED(hr)); + + memset(&queue_desc, 0, sizeof(queue_desc)); + queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + hr = ID3D12Device_CreateCommandQueue(teapot->device, &queue_desc, + &IID_ID3D12CommandQueue, (void **)&teapot->command_queue); + assert(SUCCEEDED(hr)); + + swapchain_desc.buffer_count = 2; + swapchain_desc.format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapchain_desc.width = teapot->width; + swapchain_desc.height = teapot->height; + teapot->swapchain = demo_swapchain_create(teapot->command_queue, teapot->window, &swapchain_desc); + assert(teapot->swapchain); + rt_count = demo_swapchain_get_back_buffer_count(teapot->swapchain); + teapot->swapchain_images = calloc(rt_count, sizeof(*teapot->swapchain_images)); + assert(teapot->swapchain_images); + teapot->rt_idx = demo_swapchain_get_current_back_buffer_index(teapot->swapchain); + + memset(&heap_desc, 0, sizeof(heap_desc)); + heap_desc.NumDescriptors = rt_count; + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + hr = ID3D12Device_CreateDescriptorHeap(teapot->device, &heap_desc, + &IID_ID3D12DescriptorHeap, (void **)&teapot->rtv_heap); + assert(SUCCEEDED(hr)); + + teapot->rtv_descriptor_size = ID3D12Device_GetDescriptorHandleIncrementSize(teapot->device, + D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + rtv_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(teapot->rtv_heap); + for (i = 0; i < rt_count; ++i) + { + teapot->swapchain_images[i].render_target = demo_swapchain_get_back_buffer(teapot->swapchain, i); + ID3D12Device_CreateRenderTargetView(teapot->device, + teapot->swapchain_images[i].render_target, NULL, rtv_handle); + rtv_handle.ptr += teapot->rtv_descriptor_size; + hr = ID3D12Device_CreateCommandAllocator(teapot->device, D3D12_COMMAND_LIST_TYPE_DIRECT, + &IID_ID3D12CommandAllocator, (void **)&teapot->swapchain_images[i].command_allocator); + assert(SUCCEEDED(hr)); + } +} + +static void teapot_fence_destroy(struct teapot_fence *teapot_fence) +{ + ID3D12Fence_Release(teapot_fence->fence); +} + +static void teapot_destroy_assets(struct teapot *teapot) +{ + unsigned int i; + + teapot_fence_destroy(&teapot->fence); + ID3D12Resource_Release(teapot->ib); + ID3D12Resource_Release(teapot->vb); + ID3D12Resource_Unmap(teapot->cb, 0, NULL); + ID3D12Resource_Release(teapot->cb); + for (i = 0; i < demo_swapchain_get_back_buffer_count(teapot->swapchain); ++i) + { + ID3D12GraphicsCommandList_Release(teapot->swapchain_images[i].command_list); + } + ID3D12PipelineState_Release(teapot->pipeline_state); + ID3D12RootSignature_Release(teapot->root_signature); +} + +static void teapot_fence_create(struct teapot_fence *fence, ID3D12Device *device) +{ + HRESULT hr; + + hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE, + &IID_ID3D12Fence, (void **)&fence->fence); + assert(SUCCEEDED(hr)); + fence->value = 1; +} + +static void teapot_load_mesh(struct teapot *teapot) +{ + D3D12_RESOURCE_DESC resource_desc; + D3D12_HEAP_PROPERTIES heap_desc; + struct demo_patch *patches; + struct demo_vec3 *vertices; + size_t patch_count; + HRESULT hr; + + patch_count = ARRAY_SIZE(teapot_rotate_patches) + ARRAY_SIZE(teapot_flip_patches); + + heap_desc.Type = D3D12_HEAP_TYPE_UPLOAD; + heap_desc.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heap_desc.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heap_desc.CreationNodeMask = 1; + heap_desc.VisibleNodeMask = 1; + + resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + resource_desc.Alignment = 0; + resource_desc.Width = sizeof(teapot_control_points); + resource_desc.Height = 1; + resource_desc.DepthOrArraySize = 1; + resource_desc.MipLevels = 1; + resource_desc.Format = DXGI_FORMAT_UNKNOWN; + resource_desc.SampleDesc.Count = 1; + resource_desc.SampleDesc.Quality = 0; + resource_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + resource_desc.Flags = D3D12_RESOURCE_FLAG_NONE; + + hr = ID3D12Device_CreateCommittedResource(teapot->device, &heap_desc, D3D12_HEAP_FLAG_NONE, &resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &IID_ID3D12Resource, (void **)&teapot->vb); + assert(SUCCEEDED(hr)); + + resource_desc.Width = patch_count * sizeof(*patches); + hr = ID3D12Device_CreateCommittedResource(teapot->device, &heap_desc, D3D12_HEAP_FLAG_NONE, &resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &IID_ID3D12Resource, (void **)&teapot->ib); + assert(SUCCEEDED(hr)); + + hr = ID3D12Resource_Map(teapot->vb, 0, &(D3D12_RANGE){0, 0}, (void **)&vertices); + assert(SUCCEEDED(hr)); + hr = ID3D12Resource_Map(teapot->ib, 0, &(D3D12_RANGE){0, 0}, (void **)&patches); + assert(SUCCEEDED(hr)); + + memcpy(vertices, teapot_control_points, sizeof(teapot_control_points)); + memcpy(patches, teapot_rotate_patches, sizeof(teapot_rotate_patches)); + memcpy(&patches[ARRAY_SIZE(teapot_rotate_patches)], teapot_flip_patches, sizeof(teapot_flip_patches)); + + ID3D12Resource_Unmap(teapot->ib, 0, NULL); + ID3D12Resource_Unmap(teapot->vb, 0, NULL); + + teapot->vbv.BufferLocation = ID3D12Resource_GetGPUVirtualAddress(teapot->vb); + teapot->vbv.StrideInBytes = sizeof(*teapot_control_points); + teapot->vbv.SizeInBytes = sizeof(teapot_control_points); + + teapot->ibv.BufferLocation = ID3D12Resource_GetGPUVirtualAddress(teapot->ib); + teapot->ibv.SizeInBytes = patch_count * sizeof(*patches); + teapot->ibv.Format = DXGI_FORMAT_R16_UINT; +} + +static void teapot_load_assets(struct teapot *teapot) +{ + D3D12_ROOT_SIGNATURE_DESC root_signature_desc; + D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc; + D3D12_ROOT_PARAMETER root_parameters[1]; + D3D12_RESOURCE_DESC resource_desc; + D3D12_HEAP_PROPERTIES heap_desc; + ID3DBlob *vs, *hs, *ds, *ps; + unsigned int i; + HRESULT hr; + + static const D3D12_INPUT_ELEMENT_DESC il_desc[] = + { + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + }; + + root_parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + root_parameters[0].Descriptor.ShaderRegister = 0; + root_parameters[0].Descriptor.RegisterSpace = 0; + root_parameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + memset(&root_signature_desc, 0, sizeof(root_signature_desc)); + root_signature_desc.NumParameters = 1; + root_signature_desc.pParameters = root_parameters; + root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT + | D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS + | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS + | D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS; + hr = demo_create_root_signature(teapot->device, &root_signature_desc, &teapot->root_signature); + assert(SUCCEEDED(hr)); + + hr = D3DCompile(teapot_hlsl, teapot_hlsl_size, "teapot.hlsl", + NULL, NULL, "vs_main", "vs_5_0", 0, 0, &vs, NULL); + assert(SUCCEEDED(hr)); + hr = D3DCompile(teapot_hlsl, teapot_hlsl_size, "teapot.hlsl", + NULL, NULL, "hs_main", "hs_5_0", 0, 0, &hs, NULL); + assert(SUCCEEDED(hr)); + hr = D3DCompile(teapot_hlsl, teapot_hlsl_size, "teapot.hlsl", + NULL, NULL, "ds_main", "ds_5_0", 0, 0, &ds, NULL); + assert(SUCCEEDED(hr)); + hr = D3DCompile(teapot_hlsl, teapot_hlsl_size, "teapot.hlsl", + NULL, NULL, "ps_main", "ps_5_0", 0, 0, &ps, NULL); + assert(SUCCEEDED(hr)); + + memset(&pso_desc, 0, sizeof(pso_desc)); + pso_desc.InputLayout.pInputElementDescs = il_desc; + pso_desc.InputLayout.NumElements = ARRAY_SIZE(il_desc); + pso_desc.pRootSignature = teapot->root_signature; + pso_desc.VS.pShaderBytecode = ID3D10Blob_GetBufferPointer(vs); + pso_desc.VS.BytecodeLength = ID3D10Blob_GetBufferSize(vs); + pso_desc.HS.pShaderBytecode = ID3D10Blob_GetBufferPointer(hs); + pso_desc.HS.BytecodeLength = ID3D10Blob_GetBufferSize(hs); + pso_desc.DS.pShaderBytecode = ID3D10Blob_GetBufferPointer(ds); + pso_desc.DS.BytecodeLength = ID3D10Blob_GetBufferSize(ds); + pso_desc.PS.pShaderBytecode = ID3D10Blob_GetBufferPointer(ps); + pso_desc.PS.BytecodeLength = ID3D10Blob_GetBufferSize(ps); + + demo_rasterizer_desc_init_default(&pso_desc.RasterizerState); + pso_desc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME; + pso_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; + pso_desc.RasterizerState.FrontCounterClockwise = TRUE; + demo_blend_desc_init_default(&pso_desc.BlendState); + pso_desc.SampleMask = UINT_MAX; + pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH; + pso_desc.NumRenderTargets = 1; + pso_desc.RTVFormats[0] = DXGI_FORMAT_B8G8R8A8_UNORM; + pso_desc.SampleDesc.Count = 1; + hr = ID3D12Device_CreateGraphicsPipelineState(teapot->device, &pso_desc, + &IID_ID3D12PipelineState, (void **)&teapot->pipeline_state); + assert(SUCCEEDED(hr)); + + ID3D10Blob_Release(ps); + ID3D10Blob_Release(ds); + ID3D10Blob_Release(hs); + ID3D10Blob_Release(vs); + + for (i = 0; i < demo_swapchain_get_back_buffer_count(teapot->swapchain); ++i) + { + hr = ID3D12Device_CreateCommandList(teapot->device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, + teapot->swapchain_images[i].command_allocator, teapot->pipeline_state, + &IID_ID3D12GraphicsCommandList, (void **)&teapot->swapchain_images[i].command_list); + assert(SUCCEEDED(hr)); + hr = ID3D12GraphicsCommandList_Close(teapot->swapchain_images[i].command_list); + assert(SUCCEEDED(hr)); + } + + heap_desc.Type = D3D12_HEAP_TYPE_UPLOAD; + heap_desc.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heap_desc.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heap_desc.CreationNodeMask = 1; + heap_desc.VisibleNodeMask = 1; + + resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + resource_desc.Alignment = 0; + resource_desc.Width = sizeof(*teapot->cb_data); + resource_desc.Height = 1; + resource_desc.DepthOrArraySize = 1; + resource_desc.MipLevels = 1; + resource_desc.Format = DXGI_FORMAT_UNKNOWN; + resource_desc.SampleDesc.Count = 1; + resource_desc.SampleDesc.Quality = 0; + resource_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + resource_desc.Flags = D3D12_RESOURCE_FLAG_NONE; + + hr = ID3D12Device_CreateCommittedResource(teapot->device, &heap_desc, D3D12_HEAP_FLAG_NONE, &resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &IID_ID3D12Resource, (void **)&teapot->cb); + assert(SUCCEEDED(hr)); + + hr = ID3D12Resource_Map(teapot->cb, 0, &(D3D12_RANGE){0, 0}, (void **)&teapot->cb_data); + assert(SUCCEEDED(hr)); + teapot_update_mvp(teapot); + teapot->cb_data->level = teapot->tessellation_level; + + teapot_load_mesh(teapot); + + teapot_fence_create(&teapot->fence, teapot->device); + teapot_wait_for_previous_frame(teapot); +} + +static void teapot_key_press(struct demo_window *window, demo_key key, void *user_data) +{ + struct teapot *teapot = user_data; + + switch (key) + { + case '-': + case DEMO_KEY_KP_SUBTRACT: + if (teapot->tessellation_level > 1) + teapot->cb_data->level = --teapot->tessellation_level; + break; + case '=': + case DEMO_KEY_KP_ADD: + if (teapot->tessellation_level < D3D12_TESSELLATOR_MAX_TESSELLATION_FACTOR) + teapot->cb_data->level = ++teapot->tessellation_level; + break; + case DEMO_KEY_ESCAPE: + demo_window_destroy(window); + break; + case DEMO_KEY_LEFT: + teapot->phi -= M_PI / 36.0f; + if (teapot->phi < -M_PI) + teapot->phi += 2.0f * M_PI; + teapot_update_mvp(teapot); + break; + case DEMO_KEY_RIGHT: + teapot->phi += M_PI / 36.0f; + if (teapot->phi > M_PI) + teapot->phi -= 2.0f * M_PI; + teapot_update_mvp(teapot); + break; + case DEMO_KEY_UP: + teapot->theta -= M_PI / 36.0f; + if (teapot->theta < -M_PI) + teapot->theta += 2.0f * M_PI; + teapot_update_mvp(teapot); + break; + case DEMO_KEY_DOWN: + teapot->theta += M_PI / 36.0f; + if (teapot->theta > M_PI) + teapot->theta -= 2.0f * M_PI; + teapot_update_mvp(teapot); + break; + default: + break; + } +} + +static void teapot_expose(struct demo_window *window, void *user_data) +{ + teapot_render_frame(user_data); +} + +static void teapot_idle(struct demo *demo, void *user_data) +{ + teapot_render_frame(user_data); +} + +static int teapot_main(void) +{ + unsigned int width = 800, height = 600; + struct teapot teapot; + double dpi_x, dpi_y; + + memset(&teapot, 0, sizeof(teapot)); + if (!demo_init(&teapot.demo, &teapot)) + return EXIT_FAILURE; + demo_set_idle_func(&teapot.demo, teapot_idle); + + demo_get_dpi(&teapot.demo, &dpi_x, &dpi_y); + width *= dpi_x / 96.0; + height *= dpi_y / 96.0; + teapot.window = demo_window_create(&teapot.demo, "vkd3d teapot", width, height, &teapot); + demo_window_set_key_press_func(teapot.window, teapot_key_press); + demo_window_set_expose_func(teapot.window, teapot_expose); + + teapot.width = width; + teapot.height = height; + teapot.tessellation_level = 4; + + teapot.theta = M_PI / 2.0f; + teapot.phi = -M_PI / 4.0f; + + teapot.vp.Width = width; + teapot.vp.Height = height; + teapot.vp.MaxDepth = 1.0f; + + teapot.scissor_rect.right = width; + teapot.scissor_rect.bottom = height; + + teapot_load_pipeline(&teapot); + teapot_load_assets(&teapot); + teapot_populate_command_lists(&teapot); + + printf("vkd3d-teapot: Running on \"%s\" using %s.\n", + demo_swapchain_get_device_name(teapot.swapchain), demo_get_platform_name()); + demo_process_events(&teapot.demo); + + teapot_wait_for_previous_frame(&teapot); + teapot_destroy_assets(&teapot); + teapot_destroy_pipeline(&teapot); + demo_cleanup(&teapot.demo); + + return EXIT_SUCCESS; +} + +#ifdef _WIN32 +/* Do not trigger -Wmissing-prototypes. */ +int wmain(void); + +int wmain(void) +#else +int main(void) +#endif +{ + return teapot_main(); +} diff --git a/demos/teapot.h b/demos/teapot.h new file mode 100644 index 000000000..9206022f7 --- /dev/null +++ b/demos/teapot.h @@ -0,0 +1,129 @@ +/* + * Copyright 2025 Henri Verbeet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* Teapot data adapted from freeglut's fg_teapot_data.h with the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The data is of course ultimately derived from Martin Newell's famous + * teapot, also known as the Utah teapot. + */ + +/* Rim, body, lid, and bottom data must be rotated along all four quadrants; + * handle and spout data is flipped (negate y values) only. */ + +static const struct demo_patch teapot_rotate_patches[] = +{ + {{{ 0, 1, 2, 3}, { 4, 5, 6, 7}, { 8, 9, 10, 11}, { 12, 13, 14, 15}}}, /* rim */ + {{{ 12, 13, 14, 15}, { 16, 17, 18, 19}, { 20, 21, 22, 23}, { 24, 25, 26, 27}}}, /* body */ + {{{ 24, 25, 26, 27}, { 28, 29, 30, 31}, { 32, 33, 34, 35}, { 36, 37, 38, 39}}}, + {{{ 40, 40, 40, 40}, { 43, 44, 45, 46}, { 47, 47, 47, 47}, { 48, 49, 50, 51}}}, /* lid */ + {{{ 48, 49, 50, 51}, { 52, 53, 54, 55}, { 56, 57, 58, 59}, { 60, 61, 62, 63}}}, + {{{ 64, 64, 64, 64}, { 65, 66, 67, 68}, { 69, 70, 71, 72}, { 39, 38, 37, 36}}}, /* bottom */ +}, +teapot_flip_patches[] = +{ + {{{ 73, 74, 75, 76}, { 77, 78, 79, 80}, { 81, 82, 83, 84}, { 85, 86, 87, 88}}}, /* handle */ + {{{ 85, 86, 87, 88}, { 89, 90, 91, 92}, { 93, 94, 95, 96}, { 97, 98, 99, 100}}}, + {{{101, 102, 103, 104}, {105, 106, 107, 108}, {109, 110, 111, 112}, {113, 114, 115, 116}}}, /* spout */ + {{{113, 114, 115, 116}, {117, 118, 119, 120}, {121, 122, 123, 124}, {125, 126, 127, 128}}}, +}; + +static const struct demo_vec3 teapot_control_points[] = +{ + { 1.40000f, 0.00000f, 2.40000f}, { 1.40000f, -0.78400f, 2.40000f}, + { 0.78400f, -1.40000f, 2.40000f}, { 0.00000f, -1.40000f, 2.40000f}, + { 1.33750f, 0.00000f, 2.53125f}, { 1.33750f, -0.74900f, 2.53125f}, + { 0.74900f, -1.33750f, 2.53125f}, { 0.00000f, -1.33750f, 2.53125f}, + { 1.43750f, 0.00000f, 2.53125f}, { 1.43750f, -0.80500f, 2.53125f}, + { 0.80500f, -1.43750f, 2.53125f}, { 0.00000f, -1.43750f, 2.53125f}, + { 1.50000f, 0.00000f, 2.40000f}, { 1.50000f, -0.84000f, 2.40000f}, + { 0.84000f, -1.50000f, 2.40000f}, { 0.00000f, -1.50000f, 2.40000f}, + { 1.75000f, 0.00000f, 1.87500f}, { 1.75000f, -0.98000f, 1.87500f}, + { 0.98000f, -1.75000f, 1.87500f}, { 0.00000f, -1.75000f, 1.87500f}, + { 2.00000f, 0.00000f, 1.35000f}, { 2.00000f, -1.12000f, 1.35000f}, + { 1.12000f, -2.00000f, 1.35000f}, { 0.00000f, -2.00000f, 1.35000f}, + { 2.00000f, 0.00000f, 0.90000f}, { 2.00000f, -1.12000f, 0.90000f}, + { 1.12000f, -2.00000f, 0.90000f}, { 0.00000f, -2.00000f, 0.90000f}, + { 2.00000f, 0.00000f, 0.45000f}, { 2.00000f, -1.12000f, 0.45000f}, + { 1.12000f, -2.00000f, 0.45000f}, { 0.00000f, -2.00000f, 0.45000f}, + { 1.50000f, 0.00000f, 0.22500f}, { 1.50000f, -0.84000f, 0.22500f}, + { 0.84000f, -1.50000f, 0.22500f}, { 0.00000f, -1.50000f, 0.22500f}, + { 1.50000f, 0.00000f, 0.15000f}, { 1.50000f, -0.84000f, 0.15000f}, + { 0.84000f, -1.50000f, 0.15000f}, { 0.00000f, -1.50000f, 0.15000f}, + { 0.00000f, 0.00000f, 3.15000f}, { 0.00000f, -0.00200f, 3.15000f}, + { 0.00200f, 0.00000f, 3.15000f}, { 0.80000f, 0.00000f, 3.15000f}, + { 0.80000f, -0.45000f, 3.15000f}, { 0.45000f, -0.80000f, 3.15000f}, + { 0.00000f, -0.80000f, 3.15000f}, { 0.00000f, 0.00000f, 2.85000f}, + { 0.20000f, 0.00000f, 2.70000f}, { 0.20000f, -0.11200f, 2.70000f}, + { 0.11200f, -0.20000f, 2.70000f}, { 0.00000f, -0.20000f, 2.70000f}, + { 0.40000f, 0.00000f, 2.55000f}, { 0.40000f, -0.22400f, 2.55000f}, + { 0.22400f, -0.40000f, 2.55000f}, { 0.00000f, -0.40000f, 2.55000f}, + { 1.30000f, 0.00000f, 2.55000f}, { 1.30000f, -0.72800f, 2.55000f}, + { 0.72800f, -1.30000f, 2.55000f}, { 0.00000f, -1.30000f, 2.55000f}, + { 1.30000f, 0.00000f, 2.40000f}, { 1.30000f, -0.72800f, 2.40000f}, + { 0.72800f, -1.30000f, 2.40000f}, { 0.00000f, -1.30000f, 2.40000f}, + { 0.00000f, 0.00000f, 0.00000f}, { 0.00000f, -1.42500f, 0.00000f}, + { 0.79800f, -1.42500f, 0.00000f}, { 1.42500f, -0.79800f, 0.00000f}, + { 1.42500f, 0.00000f, 0.00000f}, { 0.00000f, -1.50000f, 0.07500f}, + { 0.84000f, -1.50000f, 0.07500f}, { 1.50000f, -0.84000f, 0.07500f}, + { 1.50000f, 0.00000f, 0.07500f}, {-1.60000f, 0.00000f, 2.02500f}, + {-1.60000f, -0.30000f, 2.02500f}, {-1.50000f, -0.30000f, 2.25000f}, + {-1.50000f, 0.00000f, 2.25000f}, {-2.30000f, 0.00000f, 2.02500f}, + {-2.30000f, -0.30000f, 2.02500f}, {-2.50000f, -0.30000f, 2.25000f}, + {-2.50000f, 0.00000f, 2.25000f}, {-2.70000f, 0.00000f, 2.02500f}, + {-2.70000f, -0.30000f, 2.02500f}, {-3.00000f, -0.30000f, 2.25000f}, + {-3.00000f, 0.00000f, 2.25000f}, {-2.70000f, 0.00000f, 1.80000f}, + {-2.70000f, -0.30000f, 1.80000f}, {-3.00000f, -0.30000f, 1.80000f}, + {-3.00000f, 0.00000f, 1.80000f}, {-2.70000f, 0.00000f, 1.57500f}, + {-2.70000f, -0.30000f, 1.57500f}, {-3.00000f, -0.30000f, 1.35000f}, + {-3.00000f, 0.00000f, 1.35000f}, {-2.50000f, 0.00000f, 1.12500f}, + {-2.50000f, -0.30000f, 1.12500f}, {-2.65000f, -0.30000f, 0.93750f}, + {-2.65000f, 0.00000f, 0.93750f}, {-2.00000f, 0.00000f, 0.90000f}, + {-2.00000f, -0.30000f, 0.90000f}, {-1.90000f, -0.30000f, 0.60000f}, + {-1.90000f, 0.00000f, 0.60000f}, { 1.70000f, 0.00000f, 1.42500f}, + { 1.70000f, -0.66000f, 1.42500f}, { 1.70000f, -0.66000f, 0.60000f}, + { 1.70000f, 0.00000f, 0.60000f}, { 2.60000f, 0.00000f, 1.42500f}, + { 2.60000f, -0.66000f, 1.42500f}, { 3.10000f, -0.66000f, 0.82500f}, + { 3.10000f, 0.00000f, 0.82500f}, { 2.30000f, 0.00000f, 2.10000f}, + { 2.30000f, -0.25000f, 2.10000f}, { 2.40000f, -0.25000f, 2.02500f}, + { 2.40000f, 0.00000f, 2.02500f}, { 2.70000f, 0.00000f, 2.40000f}, + { 2.70000f, -0.25000f, 2.40000f}, { 3.30000f, -0.25000f, 2.40000f}, + { 3.30000f, 0.00000f, 2.40000f}, { 2.80000f, 0.00000f, 2.47500f}, + { 2.80000f, -0.25000f, 2.47500f}, { 3.52500f, -0.25000f, 2.49375f}, + { 3.52500f, 0.00000f, 2.49375f}, { 2.90000f, 0.00000f, 2.47500f}, + { 2.90000f, -0.15000f, 2.47500f}, { 3.45000f, -0.15000f, 2.51250f}, + { 3.45000f, 0.00000f, 2.51250f}, { 2.80000f, 0.00000f, 2.40000f}, + { 2.80000f, -0.15000f, 2.40000f}, { 3.20000f, -0.15000f, 2.40000f}, + { 3.20000f, 0.00000f, 2.40000f} +}; diff --git a/demos/teapot.hlsl b/demos/teapot.hlsl new file mode 100644 index 000000000..87675fe8f --- /dev/null +++ b/demos/teapot.hlsl @@ -0,0 +1,121 @@ +/* + * Copyright 2025 Henri Verbeet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +cbuffer teapot_cb : register(b0) +{ + float4x4 mvp_matrix; + float level; +}; + +struct control_point +{ + float4 position : SV_POSITION; +}; + +struct patch_constant_data +{ + float edges[4] : SV_TessFactor; + float inside[2] : SV_InsideTessFactor; +}; + +struct ps_in +{ + float4 position : SV_POSITION; +}; + +float4 vs_main(float4 position : POSITION, uint id : SV_InstanceID) : SV_POSITION +{ + /* Mirror/flip patches based on the instance ID. */ + position.w = -1.0; + if (id & 1) + position.yw = -position.yw; + if (id & 2) + position.xw = -position.xw; + + return position; +} + +struct patch_constant_data patch_constant(InputPatch input) +{ + struct patch_constant_data output; + + output.edges[0] = level; + output.edges[1] = level; + output.edges[2] = level; + output.edges[3] = level; + output.inside[0] = level; + output.inside[1] = level; + + return output; +} + +[domain("quad")] +[outputcontrolpoints(16)] +[outputtopology("triangle_ccw")] +[partitioning("integer")] +[patchconstantfunc("patch_constant")] +struct control_point hs_main(InputPatch input, uint i : SV_OutputControlPointID) +{ + /* Reorder mirrored/flipped control points. */ + if (input[0].position.w < 0.0) + { + uint u = i % 4, v = i / 4; + return input[v * 4 + (3 - u)]; + } + + return input[i]; +} + +float3 eval_quadratic(float3 p0, float3 p1, float3 p2, float t) +{ + return lerp(lerp(p0, p1, t), lerp(p1, p2, t), t); +} + +float3 eval_cubic(float3 p0, float3 p1, float3 p2, float3 p3, float t) +{ + return lerp(eval_quadratic(p0, p1, p2, t), + eval_quadratic(p1, p2, p3, t), t); +} + +struct ps_in eval_patch(float2 t, float4 p[16]) +{ + float3 position, q[4]; + struct ps_in o; + + q[0] = eval_cubic(p[0].xyz, p[4].xyz, p[8].xyz, p[12].xyz, t.y); + q[1] = eval_cubic(p[1].xyz, p[5].xyz, p[9].xyz, p[13].xyz, t.y); + q[2] = eval_cubic(p[2].xyz, p[6].xyz, p[10].xyz, p[14].xyz, t.y); + q[3] = eval_cubic(p[3].xyz, p[7].xyz, p[11].xyz, p[15].xyz, t.y); + + position = eval_cubic(q[0], q[1], q[2], q[3], t.x); + o.position = mul(mvp_matrix, float4(position, 1.0)); + + return o; +} + +[domain("quad")] +struct ps_in ds_main(struct patch_constant_data input, + float2 tess_coord : SV_DomainLocation, const OutputPatch patch) +{ + return eval_patch(tess_coord, patch); +} + +float4 ps_main(struct ps_in i) : SV_TARGET +{ + return float4(1.0, 0.69, 0.0, 1.0); +}