diff --git a/Makefile.am b/Makefile.am index 6f8df298..6f1adc89 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,11 +19,29 @@ vkd3d_public_headers = \ include/vkd3d_utils.h \ include/vkd3d_windows.h +spv_triangle_shaders = \ + demos/triangle.vert.spv \ + demos/triangle.frag.spv + +spv_shaders = \ + $(spv_triangle_shaders) + +hlsl_shaders = \ + demos/triangle.hlsl + vkd3d_tests = \ tests/d3d12 +vkd3d_demos = \ + demos/triangle + +vkd3d_demos_headers = \ + demos/demo.h \ + demos/demo_win32.h \ + demos/demo_xcb.h + BUILT_SOURCES = $(widl_headers) -CLEANFILES = $(widl_headers) +CLEANFILES = $(widl_headers) $(spv_shaders) noinst_LTLIBRARIES = libvkd3d-common.la libvkd3d_common_la_SOURCES = \ @@ -66,6 +84,11 @@ AM_DEFAULT_SOURCE_EXT = .c TESTS = $(vkd3d_tests) tests_d3d12_LDADD = $(LDADD) @PTHREAD_LIBS@ +noinst_PROGRAMS = $(vkd3d_demos) +EXTRA_DIST += $(vkd3d_demos_headers) +EXTRA_demos_triangle_DEPENDENCIES = $(spv_triangle_shaders) +demos_triangle_LDADD = libvkd3d.la $(LDADD) @XCB_LIBS@ + VKD3D_V_WIDL = $(vkd3d_v_widl_@AM_V@) vkd3d_v_widl_ = $(vkd3d_v_widl_@AM_DEFAULT_V@) vkd3d_v_widl_0 = @echo " WIDL " $@; @@ -75,6 +98,17 @@ EXTRA_DIST += $(widl_headers) $(widl_headers:.h=.idl) $(widl_headers): %.h: %.idl $(VKD3D_V_WIDL)$(WIDL) -o $@ $< +VKD3D_V_GLSLANG = $(vkd3d_v_glslang_@AM_V@) +vkd3d_v_glslang_ = $(vkd3d_v_glslang_@AM_DEFAULT_V@) +vkd3d_v_glslang_0 = @echo " GLSLANG " $@; +vkd3d_v_glslang_1 = + +EXTRA_DIST += $(spv_shaders:.spv=) +$(spv_shaders): %.spv: % + $(VKD3D_V_GLSLANG)$(GLSLANG) -V -o $@ $< + +EXTRA_DIST += $(hlsl_shaders) + libvkd3d.pc: $(srcdir)/libs/vkd3d/libvkd3d.pc.in sed -e 's![@]prefix[@]!$(prefix)!g' \ -e 's![@]exec_prefix[@]!$(exec_prefix)!g' \ @@ -93,13 +127,14 @@ if HAS_CROSSTARGET32 CROSS32_CC = @CROSSCC32@ CROSS32_DLLTOOL = @CROSSTARGET32@-dlltool CROSS32_IMPLIBS = $(cross_implibs:=.cross32.a) -CROSS32_EXEFILES = $(vkd3d_tests:=.cross32.exe) +CROSS32_EXEFILES = $(vkd3d_tests:=.cross32.exe) $(vkd3d_demos:=.cross32.exe) CROSS32_FILES = $(CROSS32_IMPLIBS) $(CROSS32_EXEFILES) CLEANFILES += $(CROSS32_FILES) crosstest32: $(CROSS32_FILES) -include tests/$(DEPDIR)/*.cross32.Po +-include demos/$(DEPDIR)/*.cross32.Po $(CROSS32_IMPLIBS): %.cross32.a: %.cross32.def @${MKDIR_P} crosslibs @@ -107,7 +142,7 @@ $(CROSS32_IMPLIBS): %.cross32.a: %.cross32.def $(CROSS32_EXEFILES): %.cross32.exe: %.c $(CROSS32_IMPLIBS) $(widl_headers) $(AM_V_CCLD)depbase=`echo $@ | sed 's![^/]*$$!$(DEPDIR)/&!;s!\.exe$$!!'`; \ - $(CROSS32_CC) $(CROSS_CFLAGS) -MT $@ -MD -MP -MF $$depbase.Tpo -o $@ $< $(CROSS32_IMPLIBS) && \ + $(CROSS32_CC) $(CROSS_CFLAGS) -MT $@ -MD -MP -MF $$depbase.Tpo -o $@ $< $(CROSS32_IMPLIBS) -ldxgi -lgdi32 && \ $(am__mv) $$depbase.Tpo $$depbase.Po else crosstest32: @@ -117,13 +152,14 @@ if HAS_CROSSTARGET64 CROSS64_CC = @CROSSCC64@ CROSS64_DLLTOOL = @CROSSTARGET64@-dlltool CROSS64_IMPLIBS = $(cross_implibs:=.cross64.a) -CROSS64_EXEFILES = $(vkd3d_tests:=.cross64.exe) +CROSS64_EXEFILES = $(vkd3d_tests:=.cross64.exe) $(vkd3d_demos:=.cross64.exe) CROSS64_FILES = $(CROSS64_IMPLIBS) $(CROSS64_EXEFILES) CLEANFILES += $(CROSS64_FILES) crosstest64: $(CROSS64_FILES) -include tests/$(DEPDIR)/*.cross64.Po +-include demos/$(DEPDIR)/*.cross64.Po $(CROSS64_IMPLIBS): %.cross64.a: %.cross64.def @${MKDIR_P} crosslibs @@ -131,7 +167,7 @@ $(CROSS64_IMPLIBS): %.cross64.a: %.cross64.def $(CROSS64_EXEFILES): %.cross64.exe: %.c $(CROSS64_IMPLIBS) $(widl_headers) $(AM_V_CCLD)depbase=`echo $@ | sed 's![^/]*$$!$(DEPDIR)/&!;s!\.exe$$!!'`; \ - $(CROSS64_CC) $(CROSS_CFLAGS) -MT $@ -MD -MP -MF $$depbase.Tpo -o $@ $< $(CROSS64_IMPLIBS) && \ + $(CROSS64_CC) $(CROSS_CFLAGS) -MT $@ -MD -MP -MF $$depbase.Tpo -o $@ $< $(CROSS64_IMPLIBS) -ldxgi -lgdi32 && \ $(am__mv) $$depbase.Tpo $$depbase.Po else crosstest64: diff --git a/configure.ac b/configure.ac index 7ae2ce19..24f057e6 100644 --- a/configure.ac +++ b/configure.ac @@ -7,6 +7,7 @@ AC_CONFIG_LIBOBJ_DIR([portable]) AC_CONFIG_HEADERS(include/config.h) AC_ARG_VAR([WIDL], [widl IDL compiler]) +AC_ARG_VAR([GLSLANG], [glslangValidator GLSL compiler]) AC_ARG_VAR([CROSSCC32], [32-bit Windows cross compiler]) AC_ARG_VAR([CROSSCC64], [64-bit Windows cross compiler]) @@ -17,8 +18,9 @@ AM_PROG_CC_C_O AC_PROG_SED AC_PROG_MKDIR_P AC_CHECK_PROG([WIDL], [widl], [widl], [no]) - AS_IF([test "x$WIDL" = "xno"], [AC_MSG_ERROR([widl is required to build header files.])]) +AC_CHECK_PROG([GLSLANG], [glslangValidator], [glslang], [no]) +AS_IF([test "x$GLSLANG" = "xno"], [AC_MSG_ERROR([glslangValidator is required to compile shaders.])]) AM_INIT_AUTOMAKE([1.11 foreign silent-rules subdir-objects no-dist-gzip dist-xz -Wall -Werror]) AM_MAINTAINER_MODE([enable]) @@ -52,6 +54,11 @@ AC_CHECK_LIB([pthread], [pthread_create], [AC_SUBST(PTHREAD_LIBS, "-lpthread")], [AC_MSG_ERROR(libpthread not found.)]) +AC_ARG_VAR([XCB_LIBS], [linker flags for xcb]) +AC_CHECK_LIB([xcb], [xcb_connect], + [AC_SUBST(XCB_LIBS, "-lxcb")], + [AC_MSG_ERROR([libxcb not found.])]) + AC_CHECK_LIB([vulkan], [vkGetInstanceProcAddr], [], [AC_MSG_ERROR([libvulkan not found.])]) dnl Check for functions diff --git a/demos/.gitignore b/demos/.gitignore new file mode 100644 index 00000000..198d0505 --- /dev/null +++ b/demos/.gitignore @@ -0,0 +1,2 @@ +triangle +*.spv diff --git a/demos/demo.h b/demos/demo.h new file mode 100644 index 00000000..ccac8d4a --- /dev/null +++ b/demos/demo.h @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Henri Verbeet for CodeWeavers + * + * 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 THE + * AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +/* Hack for MinGW-w64 headers. + * + * We want to use WIDL C inline wrappers because some methods + * in D3D12 interfaces return aggregate objects. Unfortunately, + * WIDL C inline wrappers are broken when used with MinGW-w64 + * headers because FORCEINLINE expands to extern inline + * which leads to the "multiple storage classes in declaration + * specifiers" compiler error. + */ +#ifdef __MINGW32__ +#include <_mingw.h> +# ifdef __MINGW64_VERSION_MAJOR +# undef __forceinline +# define __forceinline __inline__ __attribute__((__always_inline__,__gnu_inline__)) +# endif +#endif + +#include +#define WIDL_C_INLINE_WRAPPERS +#define COBJMACROS +#include + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) + +struct demo_vec3 +{ + float x, y, z; +}; + +struct demo_vec4 +{ + float x, y, z, w; +}; + +struct demo_swapchain_desc +{ + unsigned int width; + unsigned int height; + unsigned int buffer_count; + DXGI_FORMAT format; +}; + +static inline void demo_rasterizer_desc_init_default(D3D12_RASTERIZER_DESC *desc) +{ + desc->FillMode = D3D12_FILL_MODE_SOLID; + desc->CullMode = D3D12_CULL_MODE_BACK; + desc->FrontCounterClockwise = FALSE; + desc->DepthBias = D3D12_DEFAULT_DEPTH_BIAS; + desc->DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; + desc->SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; + desc->DepthClipEnable = TRUE; + desc->MultisampleEnable = FALSE; + desc->AntialiasedLineEnable = FALSE; + desc->ForcedSampleCount = 0; + desc->ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; +} + +static inline void demo_blend_desc_init_default(D3D12_BLEND_DESC *desc) +{ + static const D3D12_RENDER_TARGET_BLEND_DESC rt_blend_desc = + { + .BlendEnable = FALSE, + .LogicOpEnable = FALSE, + .SrcBlend = D3D12_BLEND_ONE, + .DestBlend = D3D12_BLEND_ZERO, + .BlendOp = D3D12_BLEND_OP_ADD, + .SrcBlendAlpha = D3D12_BLEND_ONE, + .DestBlendAlpha = D3D12_BLEND_ZERO, + .BlendOpAlpha = D3D12_BLEND_OP_ADD, + .LogicOp = D3D12_LOGIC_OP_NOOP, + .RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL, + }; + unsigned int i; + + desc->AlphaToCoverageEnable = FALSE; + desc->IndependentBlendEnable = FALSE; + for (i = 0; i < ARRAY_SIZE(desc->RenderTarget); ++i) + { + desc->RenderTarget[i] = rt_blend_desc; + } +} + +#ifdef _WIN32 +#include "demo_win32.h" +#else +#include +#include "demo_xcb.h" +#endif diff --git a/demos/demo_win32.h b/demos/demo_win32.h new file mode 100644 index 00000000..2c853f66 --- /dev/null +++ b/demos/demo_win32.h @@ -0,0 +1,302 @@ +/* + * Copyright 2016 Józef Kucia for CodeWeavers + * Copyright 2016 Henri Verbeet for CodeWeavers + * + * 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 THE + * AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +#include +#include + +#define DEMO_WINDOW_CLASS_NAME L"demo_wc" + +struct demo +{ + HMODULE d3dcompiler; + HRESULT (WINAPI *compile_from_file)(const WCHAR *filename, const void *defines, void *include, + const char *entry_point, const char *profile, UINT flags1, UINT flags2, + ID3DBlob **code, ID3DBlob **errors); + size_t window_count; + bool quit; +}; + +struct demo_window +{ + HINSTANCE instance; + HWND hwnd; + struct demo *demo; + void *user_data; + void (*draw_func)(void *user_data); +}; + +struct demo_swapchain +{ + IDXGISwapChain3 *swapchain; +}; + +static inline struct demo_window *demo_window_create(struct demo *demo, const char *title, + unsigned int width, unsigned int height, void (*draw_func)(void *user_data), void *user_data) +{ + RECT rect = {0, 0, width, height}; + struct demo_window *window; + int title_size; + WCHAR *title_w; + DWORD style; + + if (!(window = malloc(sizeof(*window)))) + return NULL; + + title_size = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0); + if (!(title_w = calloc(title_size, sizeof(*title_w)))) + { + free(window); + return NULL; + } + MultiByteToWideChar(CP_UTF8, 0, title, -1, title_w, title_size); + + window->instance = GetModuleHandle(NULL); + window->draw_func = draw_func; + window->user_data = user_data; + + style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE; + AdjustWindowRect(&rect, style, FALSE); + window->hwnd = CreateWindowExW(0, DEMO_WINDOW_CLASS_NAME, title_w, style, CW_USEDEFAULT, CW_USEDEFAULT, + rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, window->instance, NULL); + free(title_w); + if (!window->hwnd) + { + free(window); + return NULL; + } + SetWindowLongPtrW(window->hwnd, GWLP_USERDATA, (LONG_PTR)window); + window->demo = demo; + ++demo->window_count; + + return window; +} + +static inline void demo_window_destroy(struct demo_window *window) +{ + if (window->hwnd) + DestroyWindow(window->hwnd); + if (!--window->demo->window_count) + window->demo->quit = true; + free(window); +} + +static inline LRESULT CALLBACK demo_window_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) +{ + struct demo_window *window = (void *)GetWindowLongPtrW(hwnd, GWLP_USERDATA); + + switch (message) + { + case WM_PAINT: + if (window && window->draw_func) + window->draw_func(window->user_data); + return 0; + + case WM_DESTROY: + window->hwnd = NULL; + demo_window_destroy(window); + return 0; + } + + return DefWindowProcW(hwnd, message, wparam, lparam); +} + +static inline void demo_process_events(struct demo *demo) +{ + MSG msg = {0}; + + while (GetMessage(&msg, NULL, 0, 0) != -1) + { + if (msg.message == WM_QUIT) + break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + if (demo->quit) + PostQuitMessage(0); + } +} + +static inline bool demo_init(struct demo *demo) +{ + WNDCLASSEXW wc; + + if (!(demo->d3dcompiler = LoadLibraryW(L"d3dcompiler_47"))) + return false; + if (!(demo->compile_from_file = (void *)GetProcAddress(demo->d3dcompiler, "D3DCompileFromFile"))) + goto fail; + + wc.cbSize = sizeof(wc); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = demo_window_proc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(NULL); + wc.hIcon = LoadIconW(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursorW(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wc.lpszMenuName = NULL; + wc.lpszClassName = DEMO_WINDOW_CLASS_NAME; + wc.hIconSm = LoadIconW(NULL, IDI_WINLOGO); + if (!RegisterClassExW(&wc)) + goto fail; + + demo->quit = false; + + return true; + +fail: + FreeLibrary(demo->d3dcompiler); + return false; +} + +static inline void demo_cleanup(struct demo *demo) +{ + UnregisterClassW(DEMO_WINDOW_CLASS_NAME, GetModuleHandle(NULL)); + FreeLibrary(demo->d3dcompiler); +} + +static inline struct demo_swapchain *demo_swapchain_create(ID3D12CommandQueue *command_queue, + struct demo_window *window, const struct demo_swapchain_desc *desc) +{ + DXGI_SWAP_CHAIN_DESC1 swapchain_desc; + struct demo_swapchain *swapchain; + IDXGISwapChain1 *swapchain1; + IDXGIFactory2 *factory; + HRESULT hr; + + if (!(swapchain = malloc(sizeof(*swapchain)))) + return NULL; + + if (FAILED(CreateDXGIFactory1(&IID_IDXGIFactory2, (void **)&factory))) + goto fail; + + memset(&swapchain_desc, 0, sizeof(swapchain_desc)); + swapchain_desc.BufferCount = desc->buffer_count; + swapchain_desc.Width = desc->width; + swapchain_desc.Height = desc->height; + swapchain_desc.Format = desc->format; + swapchain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapchain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapchain_desc.SampleDesc.Count = 1; + + hr = IDXGIFactory2_CreateSwapChainForHwnd(factory, (IUnknown *)command_queue, + window->hwnd, &swapchain_desc, NULL, NULL, &swapchain1); + IDXGIFactory2_Release(factory); + if (FAILED(hr)) + goto fail; + + hr = IDXGISwapChain1_QueryInterface(swapchain1, &IID_IDXGISwapChain3, (void **)&swapchain->swapchain); + IDXGISwapChain1_Release(swapchain1); + if (FAILED(hr)) + goto fail; + + return swapchain; + +fail: + free(swapchain); + return NULL; +} + +static inline unsigned int demo_swapchain_get_current_back_buffer_index(struct demo_swapchain *swapchain) +{ + return IDXGISwapChain3_GetCurrentBackBufferIndex(swapchain->swapchain); +} + +static inline ID3D12Resource *demo_swapchain_get_back_buffer(struct demo_swapchain *swapchain, unsigned int index) +{ + ID3D12Resource *buffer; + + if (FAILED(IDXGISwapChain3_GetBuffer(swapchain->swapchain, index, + &IID_ID3D12Resource, (void **)&buffer))) + return NULL; + + return buffer; +} + +static inline void demo_swapchain_present(struct demo_swapchain *swapchain) +{ + IDXGISwapChain3_Present(swapchain->swapchain, 1, 0); +} + +static inline void demo_swapchain_destroy(struct demo_swapchain *swapchain) +{ + IDXGISwapChain3_Release(swapchain->swapchain); + free(swapchain); +} + +static inline HANDLE demo_create_event(void) +{ + return CreateEventA(NULL, FALSE, FALSE, NULL); +} + +static inline unsigned int demo_wait_event(HANDLE event, unsigned int ms) +{ + return WaitForSingleObject(event, ms); +} + +static inline void demo_destroy_event(HANDLE event) +{ + CloseHandle(event); +} + +static inline HRESULT demo_create_root_signature(ID3D12Device *device, + const D3D12_ROOT_SIGNATURE_DESC *desc, ID3D12RootSignature **signature) +{ + ID3DBlob *blob; + HRESULT hr; + + if (FAILED(hr = D3D12SerializeRootSignature(desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, NULL))) + return hr; + hr = ID3D12Device_CreateRootSignature(device, 0, ID3D10Blob_GetBufferPointer(blob), + ID3D10Blob_GetBufferSize(blob), &IID_ID3D12RootSignature, (void **)signature); + ID3D10Blob_Release(blob); + + return hr; +} + +static inline bool demo_load_shader(struct demo *demo, const wchar_t *hlsl_name, const char *entry_point, + const char *profile, const char *spv_name, D3D12_SHADER_BYTECODE *shader) +{ + ID3D10Blob *blob, *errors; + HRESULT hr; + + hr = demo->compile_from_file(hlsl_name, NULL, NULL, entry_point, profile, 0, 0, &blob, &errors); + if (errors) + { + fprintf(stderr, "%.*s\n", (int)ID3D10Blob_GetBufferSize(errors), (char *)ID3D10Blob_GetBufferPointer(errors)); + ID3D10Blob_Release(errors); + } + if (FAILED(hr)) + return false; + + shader->BytecodeLength = ID3D10Blob_GetBufferSize(blob); + if (!(shader->pShaderBytecode = malloc(shader->BytecodeLength))) + { + ID3D10Blob_Release(blob); + return false; + } + + memcpy((void *)shader->pShaderBytecode, ID3D10Blob_GetBufferPointer(blob), shader->BytecodeLength); + + ID3D10Blob_Release(blob); + return true; +} diff --git a/demos/demo_xcb.h b/demos/demo_xcb.h new file mode 100644 index 00000000..6da32b6b --- /dev/null +++ b/demos/demo_xcb.h @@ -0,0 +1,543 @@ +/* + * Copyright 2016 Józef Kucia for CodeWeavers + * Copyright 2016 Henri Verbeet for CodeWeavers + * + * 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 THE + * AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +#define VK_USE_PLATFORM_XCB_KHR +#include +#include +#include + +struct demo +{ + xcb_connection_t *connection; + xcb_atom_t wm_protocols_atom; + xcb_atom_t wm_delete_window_atom; + + struct demo_window **windows; + size_t windows_size; + size_t window_count; +}; + +struct demo_window +{ + xcb_window_t window; + struct demo *demo; + + void *user_data; + void (*draw_func)(void *user_data); +}; + +struct demo_swapchain +{ + VkSurfaceKHR vk_surface; + VkSwapchainKHR vk_swapchain; + VkFence vk_fence; + + VkInstance vk_instance; + VkDevice vk_device; + VkQueue vk_queue; + + uint32_t current_buffer; + unsigned int buffer_count; + ID3D12Resource *buffers[1]; +}; + +static inline xcb_atom_t demo_get_atom(xcb_connection_t *c, const char *name) +{ + xcb_intern_atom_cookie_t cookie; + xcb_intern_atom_reply_t *reply; + xcb_atom_t atom = XCB_NONE; + + cookie = xcb_intern_atom(c, 0, strlen(name), name); + if ((reply = xcb_intern_atom_reply(c, cookie, NULL))) + { + atom = reply->atom; + free(reply); + } + + return atom; +} + +static inline bool demo_add_window(struct demo *demo, struct demo_window *window) +{ + if (demo->window_count == demo->windows_size) + { + size_t new_capacity; + void *new_elements; + + new_capacity = max(demo->windows_size * 2, 4); + if (!(new_elements = realloc(demo->windows, new_capacity * sizeof(*demo->windows)))) + return false; + demo->windows = new_elements; + demo->windows_size = new_capacity; + } + + demo->windows[demo->window_count++] = window; + + return true; +} + +static inline void demo_remove_window(struct demo *demo, const struct demo_window *window) +{ + size_t i; + + for (i = 0; i < demo->window_count; ++i) + { + if (demo->windows[i] != window) + continue; + + --demo->window_count; + memmove(&demo->windows[i], &demo->windows[i + 1], (demo->window_count - i) * sizeof(*demo->windows)); + break; + } +} + +static inline struct demo_window *demo_find_window(struct demo *demo, xcb_window_t window) +{ + size_t i; + + for (i = 0; i < demo->window_count; ++i) + { + if (demo->windows[i]->window == window) + return demo->windows[i]; + } + + return NULL; +} + +static inline struct demo_window *demo_window_create(struct demo *demo, const char *title, + unsigned int width, unsigned int height, void (*draw_func)(void *user_data), void *user_data) +{ + static const uint32_t window_events = XCB_EVENT_MASK_EXPOSURE; + + struct demo_window *window; + xcb_size_hints_t hints; + xcb_screen_t *screen; + + if (!(window = malloc(sizeof(*window)))) + return NULL; + + if (!demo_add_window(demo, window)) + { + free(window); + return NULL; + } + + window->window = xcb_generate_id(demo->connection); + window->demo = demo; + window->draw_func = draw_func; + window->user_data = user_data; + screen = xcb_setup_roots_iterator(xcb_get_setup(demo->connection)).data; + xcb_create_window(demo->connection, XCB_COPY_FROM_PARENT, window->window, screen->root, 0, 0, + width, height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, + XCB_CW_EVENT_MASK, &window_events); + xcb_change_property(demo->connection, XCB_PROP_MODE_REPLACE, window->window, XCB_ATOM_WM_NAME, + XCB_ATOM_STRING, 8, strlen(title), title); + xcb_change_property(demo->connection, XCB_PROP_MODE_REPLACE, window->window, demo->wm_protocols_atom, + XCB_ATOM_ATOM, 32, 1, &demo->wm_delete_window_atom); + hints.flags = XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | XCB_ICCCM_SIZE_HINT_P_MAX_SIZE; + hints.min_width = width; + hints.min_height = height; + hints.max_width = width; + hints.max_height = height; + xcb_change_property(demo->connection, XCB_PROP_MODE_REPLACE, window->window, XCB_ATOM_WM_NORMAL_HINTS, + XCB_ATOM_WM_SIZE_HINTS, 32, sizeof(hints) >> 2, &hints); + + xcb_map_window(demo->connection, window->window); + + return window; +} + +static inline void demo_window_destroy(struct demo_window *window) +{ + xcb_destroy_window(window->demo->connection, window->window); + xcb_flush(window->demo->connection); + demo_remove_window(window->demo, window); + free(window); +} + +static inline void demo_process_events(struct demo *demo) +{ + const xcb_client_message_event_t *client_message; + xcb_generic_event_t *event; + struct demo_window *window; + bool done = false; + + xcb_flush(demo->connection); + + while (!done && (event = xcb_wait_for_event(demo->connection))) + { + switch (XCB_EVENT_RESPONSE_TYPE(event)) + { + case XCB_EXPOSE: + if ((window = demo_find_window(demo, ((struct xcb_expose_event_t *)event)->window))) + window->draw_func(window->user_data); + break; + + case XCB_CLIENT_MESSAGE: + client_message = (xcb_client_message_event_t *)event; + if (client_message->type == demo->wm_protocols_atom + && client_message->data.data32[0] == demo->wm_delete_window_atom + && (window = demo_find_window(demo, client_message->window))) + { + demo_window_destroy(window); + if (!demo->window_count) + done = true; + } + break; + } + + free(event); + } +} + +static inline bool demo_init(struct demo *demo) +{ + if (!(demo->connection = xcb_connect(NULL, NULL))) + return false; + if (xcb_connection_has_error(demo->connection) > 0) + goto fail; + if ((demo->wm_delete_window_atom = demo_get_atom(demo->connection, "WM_DELETE_WINDOW")) == XCB_NONE) + goto fail; + if ((demo->wm_protocols_atom = demo_get_atom(demo->connection, "WM_PROTOCOLS")) == XCB_NONE) + goto fail; + + demo->windows = NULL; + demo->windows_size = 0; + demo->window_count = 0; + + return true; + +fail: + xcb_disconnect(demo->connection); + return false; +} + +static inline void demo_cleanup(struct demo *demo) +{ + free(demo->windows); + xcb_disconnect(demo->connection); +} + +static inline DXGI_FORMAT demo_get_srgb_format(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_B8G8R8A8_UNORM: + return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + default: + return DXGI_FORMAT_UNKNOWN; + } +} + +static inline struct demo_swapchain *demo_swapchain_create(ID3D12CommandQueue *command_queue, + struct demo_window *window, const struct demo_swapchain_desc *desc) +{ + struct VkSwapchainCreateInfoKHR vk_swapchain_desc; + struct VkXcbSurfaceCreateInfoKHR surface_desc; + VkSwapchainKHR vk_swapchain = VK_NULL_HANDLE; + uint32_t format_count, queue_family_index; + VkSurfaceCapabilitiesKHR surface_caps; + VkPhysicalDevice vk_physical_device; + D3D12_RESOURCE_DESC resource_desc; + VkFence vk_fence = VK_NULL_HANDLE; + struct demo_swapchain *swapchain; + unsigned int image_count, i, j; + VkFenceCreateInfo fence_desc; + VkSurfaceFormatKHR *formats; + ID3D12Device *d3d12_device; + VkSurfaceKHR vk_surface; + VkInstance vk_instance; + VkBool32 supported; + VkDevice vk_device; + VkImage *vk_images; + VkFormat format; + + if ((format = vkd3d_get_vk_format(demo_get_srgb_format(desc->format))) == VK_FORMAT_UNDEFINED) + return NULL; + + if (FAILED(ID3D12CommandQueue_GetDevice(command_queue, &IID_ID3D12Device, (void **)&d3d12_device))) + return NULL; + + vk_instance = vkd3d_get_vk_instance(d3d12_device); + vk_physical_device = vkd3d_get_vk_physical_device(d3d12_device); + vk_device = vkd3d_get_vk_device(d3d12_device); + + surface_desc.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + surface_desc.pNext = NULL; + surface_desc.flags = 0; + surface_desc.connection = window->demo->connection; + surface_desc.window = window->window; + if (vkCreateXcbSurfaceKHR(vk_instance, &surface_desc, NULL, &vk_surface) < 0) + { + ID3D12Device_Release(d3d12_device); + return NULL; + } + + queue_family_index = vkd3d_get_vk_queue_family_index(command_queue); + if (vkGetPhysicalDeviceSurfaceSupportKHR(vk_physical_device, + queue_family_index, vk_surface, &supported) < 0 || !supported) + goto fail; + + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_physical_device, vk_surface, &surface_caps) < 0) + goto fail; + + if ((surface_caps.maxImageCount && desc->buffer_count > surface_caps.maxImageCount) + || desc->buffer_count < surface_caps.minImageCount + || desc->width > surface_caps.maxImageExtent.width || desc->width < surface_caps.minImageExtent.width + || desc->height > surface_caps.maxImageExtent.height || desc->height < surface_caps.minImageExtent.height + || !(surface_caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)) + goto fail; + + if (vkGetPhysicalDeviceSurfaceFormatsKHR(vk_physical_device, vk_surface, &format_count, NULL) < 0 + || !format_count || !(formats = calloc(format_count, sizeof(*formats)))) + goto fail; + + if (vkGetPhysicalDeviceSurfaceFormatsKHR(vk_physical_device, vk_surface, &format_count, formats) < 0) + { + free(formats); + goto fail; + } + + if (format_count != 1 || formats->format != VK_FORMAT_UNDEFINED + || formats->colorSpace != VK_COLORSPACE_SRGB_NONLINEAR_KHR) + { + for (i = 0; i < format_count; ++i) + { + if (formats[i].format == format && formats[i].colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) + break; + } + + if (i == format_count) + { + free(formats); + goto fail; + } + } + + free(formats); + formats = NULL; + + vk_swapchain_desc.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + vk_swapchain_desc.pNext = NULL; + vk_swapchain_desc.flags = 0; + vk_swapchain_desc.surface = vk_surface; + vk_swapchain_desc.minImageCount = desc->buffer_count; + vk_swapchain_desc.imageFormat = format; + vk_swapchain_desc.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + vk_swapchain_desc.imageExtent.width = desc->width; + vk_swapchain_desc.imageExtent.height = desc->height; + vk_swapchain_desc.imageArrayLayers = 1; + vk_swapchain_desc.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + vk_swapchain_desc.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + vk_swapchain_desc.queueFamilyIndexCount = 0; + vk_swapchain_desc.pQueueFamilyIndices = NULL; + vk_swapchain_desc.preTransform = surface_caps.currentTransform; + vk_swapchain_desc.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + vk_swapchain_desc.presentMode = VK_PRESENT_MODE_FIFO_KHR; + vk_swapchain_desc.clipped = VK_TRUE; + vk_swapchain_desc.oldSwapchain = VK_NULL_HANDLE; + if (vkCreateSwapchainKHR(vk_device, &vk_swapchain_desc, NULL, &vk_swapchain) < 0) + goto fail; + + fence_desc.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fence_desc.pNext = NULL; + fence_desc.flags = 0; + if (vkCreateFence(vk_device, &fence_desc, NULL, &vk_fence) < 0) + goto fail; + + if (vkGetSwapchainImagesKHR(vk_device, vk_swapchain, &image_count, NULL) < 0 + || !(vk_images = calloc(image_count, sizeof(*vk_images)))) + goto fail; + + if (vkGetSwapchainImagesKHR(vk_device, vk_swapchain, &image_count, vk_images) < 0) + { + free(vk_images); + goto fail; + } + + if (!(swapchain = malloc(offsetof(struct demo_swapchain, buffers[image_count])))) + { + free(vk_images); + goto fail; + } + swapchain->vk_surface = vk_surface; + swapchain->vk_swapchain = vk_swapchain; + swapchain->vk_fence = vk_fence; + swapchain->vk_instance = vk_instance; + swapchain->vk_device = vk_device; + swapchain->vk_queue = vkd3d_get_vk_queue(command_queue); + + vkAcquireNextImageKHR(vk_device, vk_swapchain, UINT64_MAX, + VK_NULL_HANDLE, vk_fence, &swapchain->current_buffer); + vkWaitForFences(vk_device, 1, &vk_fence, VK_TRUE, UINT64_MAX); + vkResetFences(vk_device, 1, &vk_fence); + + resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resource_desc.Alignment = 0; + resource_desc.Width = desc->width; + resource_desc.Height = desc->height; + resource_desc.DepthOrArraySize = 1; + resource_desc.MipLevels = 1; + resource_desc.Format = desc->format; + resource_desc.SampleDesc.Count = 1; + resource_desc.SampleDesc.Quality = 0; + resource_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resource_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + for (i = 0; i < image_count; ++i) + { + if (FAILED(vkd3d_create_image_resource(d3d12_device, + &resource_desc, vk_images[i], &swapchain->buffers[i]))) + { + for (j = 0; j < i; ++j) + { + ID3D12Resource_Release(swapchain->buffers[j]); + } + free(swapchain); + free(vk_images); + goto fail; + } + } + swapchain->buffer_count = image_count; + free(vk_images); + ID3D12Device_Release(d3d12_device); + + return swapchain; + +fail: + if (vk_fence != VK_NULL_HANDLE) + vkDestroyFence(vk_device, vk_fence, NULL); + if (vk_swapchain != VK_NULL_HANDLE) + vkDestroySwapchainKHR(vk_device, vk_swapchain, NULL); + vkDestroySurfaceKHR(vk_instance, vk_surface, NULL); + ID3D12Device_Release(d3d12_device); + return NULL; +} + +static inline unsigned int demo_swapchain_get_current_back_buffer_index(struct demo_swapchain *swapchain) +{ + return swapchain->current_buffer; +} + +static inline ID3D12Resource *demo_swapchain_get_back_buffer(struct demo_swapchain *swapchain, unsigned int index) +{ + ID3D12Resource *resource = NULL; + + if (index < swapchain->buffer_count && (resource = swapchain->buffers[index])) + ID3D12Resource_AddRef(resource); + + return resource; +} + +static inline void demo_swapchain_present(struct demo_swapchain *swapchain) +{ + VkPresentInfoKHR present_desc; + + present_desc.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_desc.pNext = NULL; + present_desc.waitSemaphoreCount = 0; + present_desc.pWaitSemaphores = NULL; + present_desc.swapchainCount = 1; + present_desc.pSwapchains = &swapchain->vk_swapchain; + present_desc.pImageIndices = &swapchain->current_buffer; + present_desc.pResults = NULL; + + vkQueuePresentKHR(swapchain->vk_queue, &present_desc); + + vkAcquireNextImageKHR(swapchain->vk_device, swapchain->vk_swapchain, UINT64_MAX, + VK_NULL_HANDLE, swapchain->vk_fence, &swapchain->current_buffer); + vkWaitForFences(swapchain->vk_device, 1, &swapchain->vk_fence, VK_TRUE, UINT64_MAX); + vkResetFences(swapchain->vk_device, 1, &swapchain->vk_fence); +} + +static inline void demo_swapchain_destroy(struct demo_swapchain *swapchain) +{ + unsigned int i; + + for (i = 0; i < swapchain->buffer_count; ++i) + { + ID3D12Resource_Release(swapchain->buffers[i]); + } + vkDestroyFence(swapchain->vk_device, swapchain->vk_fence, NULL); + vkDestroySwapchainKHR(swapchain->vk_device, swapchain->vk_swapchain, NULL); + vkDestroySurfaceKHR(swapchain->vk_instance, swapchain->vk_surface, NULL); + free(swapchain); +} + +static inline HANDLE demo_create_event(void) +{ + return vkd3d_create_event(); +} + +static inline unsigned int demo_wait_event(HANDLE event, unsigned int ms) +{ + return vkd3d_wait_event(event, ms); +} + +static inline void demo_destroy_event(HANDLE event) +{ + vkd3d_destroy_event(event); +} + +static inline HRESULT demo_create_root_signature(ID3D12Device *device, + const D3D12_ROOT_SIGNATURE_DESC *desc, ID3D12RootSignature **signature) +{ + return ID3D12Device_CreateRootSignature(device, 0, desc, ~(SIZE_T)0, + &IID_ID3D12RootSignature, (void **)signature); +} + +static inline bool demo_load_shader(struct demo *demo, const wchar_t *hlsl_name, const char *entry_point, + const char *profile, const char *spv_name, D3D12_SHADER_BYTECODE *shader) +{ + size_t data_size; + struct stat st; + ssize_t res; + void *data; + int fd; + + if ((fd = open(spv_name, O_RDONLY)) == -1) + return false; + + if (fstat(fd, &st) == -1) + goto fail; + + data_size = st.st_size; + if (!(data = malloc(data_size))) + goto fail; + + res = read(fd, data, data_size); + close(fd); + if (res != data_size) + { + free(data); + return false; + } + + shader->pShaderBytecode = data; + shader->BytecodeLength = data_size; + + return true; + +fail: + close(fd); + return false; +} diff --git a/demos/triangle.c b/demos/triangle.c new file mode 100644 index 00000000..6c14fb1e --- /dev/null +++ b/demos/triangle.c @@ -0,0 +1,410 @@ +/* + * Copyright 2016 Henri Verbeet for CodeWeavers + * + * 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 THE + * AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +/* + * This application contains code derived from Microsoft's "HelloTriangle" + * demo, the license for which follows: + * + * Copyright (c) 2015 Microsoft + * + * 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 THE + * AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +#define INITGUID +#include +#include +#include +#include +#include +#include +#include "demo.h" + +struct cxt_fence +{ + ID3D12Fence *fence; + UINT64 value; + HANDLE event; +}; + +struct cx_triangle +{ + struct demo demo; + + struct demo_window *window; + + unsigned int width; + unsigned int height; + float aspect_ratio; + + D3D12_VIEWPORT vp; + D3D12_RECT scissor_rect; + + ID3D12Device *device; + ID3D12CommandQueue *command_queue; + struct demo_swapchain *swapchain; + ID3D12DescriptorHeap *rtv_heap; + unsigned int rtv_descriptor_size; + ID3D12Resource *render_targets[2]; + ID3D12CommandAllocator *command_allocator; + + ID3D12RootSignature *root_signature; + ID3D12PipelineState *pipeline_state; + ID3D12GraphicsCommandList *command_list; + ID3D12Resource *vb; + D3D12_VERTEX_BUFFER_VIEW vbv; + + unsigned int frame_idx; + struct cxt_fence fence; +}; + +static void cxt_populate_command_list(struct cx_triangle *cxt) +{ + static const float clear_colour[] = {0.0f, 0.2f, 0.4f, 1.0f}; + + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle; + D3D12_RESOURCE_BARRIER barrier; + HRESULT hr; + + hr = ID3D12CommandAllocator_Reset(cxt->command_allocator); + assert(SUCCEEDED(hr)); + + hr = ID3D12GraphicsCommandList_Reset(cxt->command_list, cxt->command_allocator, cxt->pipeline_state); + assert(SUCCEEDED(hr)); + + ID3D12GraphicsCommandList_SetGraphicsRootSignature(cxt->command_list, cxt->root_signature); + ID3D12GraphicsCommandList_RSSetViewports(cxt->command_list, 1, &cxt->vp); + ID3D12GraphicsCommandList_RSSetScissorRects(cxt->command_list, 1, &cxt->scissor_rect); + + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = cxt->render_targets[cxt->frame_idx]; + 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(cxt->command_list, 1, &barrier); + + rtv_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(cxt->rtv_heap); + rtv_handle.ptr += cxt->frame_idx * cxt->rtv_descriptor_size; + ID3D12GraphicsCommandList_OMSetRenderTargets(cxt->command_list, 1, &rtv_handle, FALSE, NULL); + + ID3D12GraphicsCommandList_ClearRenderTargetView(cxt->command_list, rtv_handle, clear_colour, 0, NULL); + ID3D12GraphicsCommandList_IASetPrimitiveTopology(cxt->command_list, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + ID3D12GraphicsCommandList_IASetVertexBuffers(cxt->command_list, 0, 1, &cxt->vbv); + ID3D12GraphicsCommandList_DrawInstanced(cxt->command_list, 3, 1, 0, 0); + + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + ID3D12GraphicsCommandList_ResourceBarrier(cxt->command_list, 1, &barrier); + + hr = ID3D12GraphicsCommandList_Close(cxt->command_list); + assert(SUCCEEDED(hr)); +} + +static void cxt_wait_for_previous_frame(struct cx_triangle *cxt) +{ + struct cxt_fence *fence = &cxt->fence; + const UINT64 v = fence->value; + HRESULT hr; + + hr = ID3D12CommandQueue_Signal(cxt->command_queue, fence->fence, v); + assert(SUCCEEDED(hr)); + + ++fence->value; + + if (ID3D12Fence_GetCompletedValue(fence->fence) < v) + { + ID3D12Fence_SetEventOnCompletion(fence->fence, v, fence->event); + demo_wait_event(fence->event, INFINITE); + } + + cxt->frame_idx = demo_swapchain_get_current_back_buffer_index(cxt->swapchain); +} + +static void cxt_render_frame(void *user_data) +{ + struct cx_triangle *cxt = user_data; + + cxt_populate_command_list(cxt); + ID3D12CommandQueue_ExecuteCommandLists(cxt->command_queue, 1, (ID3D12CommandList **)&cxt->command_list); + demo_swapchain_present(cxt->swapchain); + cxt_wait_for_previous_frame(cxt); +} + +static void cxt_destroy_pipeline(struct cx_triangle *cxt) +{ + unsigned int i; + + ID3D12CommandAllocator_Release(cxt->command_allocator); + for (i = 0; i < ARRAY_SIZE(cxt->render_targets); ++i) + { + ID3D12Resource_Release(cxt->render_targets[i]); + } + ID3D12DescriptorHeap_Release(cxt->rtv_heap); + demo_swapchain_destroy(cxt->swapchain); + ID3D12CommandQueue_Release(cxt->command_queue); + ID3D12Device_Release(cxt->device); +} + +static void cxt_load_pipeline(struct cx_triangle *cxt) +{ + struct demo_swapchain_desc swapchain_desc; + D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc; + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle; + D3D12_COMMAND_QUEUE_DESC queue_desc; + unsigned int i; + HRESULT hr; + + hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12Device, (void **)&cxt->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(cxt->device, &queue_desc, + &IID_ID3D12CommandQueue, (void **)&cxt->command_queue); + assert(SUCCEEDED(hr)); + + swapchain_desc.buffer_count = ARRAY_SIZE(cxt->render_targets); + swapchain_desc.format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapchain_desc.width = cxt->width; + swapchain_desc.height = cxt->height; + cxt->swapchain = demo_swapchain_create(cxt->command_queue, cxt->window, &swapchain_desc); + assert(cxt->swapchain); + cxt->frame_idx = demo_swapchain_get_current_back_buffer_index(cxt->swapchain); + + memset(&rtv_heap_desc, 0, sizeof(rtv_heap_desc)); + rtv_heap_desc.NumDescriptors = ARRAY_SIZE(cxt->render_targets); + rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + hr = ID3D12Device_CreateDescriptorHeap(cxt->device, &rtv_heap_desc, + &IID_ID3D12DescriptorHeap, (void **)&cxt->rtv_heap); + assert(SUCCEEDED(hr)); + + cxt->rtv_descriptor_size = ID3D12Device_GetDescriptorHandleIncrementSize(cxt->device, + D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + rtv_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(cxt->rtv_heap); + for (i = 0; i < ARRAY_SIZE(cxt->render_targets); ++i) + { + cxt->render_targets[i] = demo_swapchain_get_back_buffer(cxt->swapchain, i); + ID3D12Device_CreateRenderTargetView(cxt->device, cxt->render_targets[i], NULL, rtv_handle); + rtv_handle.ptr += cxt->rtv_descriptor_size; + } + + hr = ID3D12Device_CreateCommandAllocator(cxt->device, D3D12_COMMAND_LIST_TYPE_DIRECT, + &IID_ID3D12CommandAllocator, (void **)&cxt->command_allocator); + assert(SUCCEEDED(hr)); +} + +static void cxt_fence_destroy(struct cxt_fence *cxt_fence) +{ + ID3D12Fence_Release(cxt_fence->fence); + demo_destroy_event(cxt_fence->event); +} + +static void cxt_destroy_assets(struct cx_triangle *cxt) +{ + cxt_fence_destroy(&cxt->fence); + ID3D12Resource_Release(cxt->vb); + ID3D12GraphicsCommandList_Release(cxt->command_list); + ID3D12PipelineState_Release(cxt->pipeline_state); + ID3D12RootSignature_Release(cxt->root_signature); +} + +static void cxt_load_shaders(struct cx_triangle *cxt, D3D12_SHADER_BYTECODE *vs, D3D12_SHADER_BYTECODE *ps) +{ + bool ret; + + ret = demo_load_shader(&cxt->demo, L"triangle.hlsl", "vs_main", "vs_5_0", "triangle.vert.spv", vs); + assert(ret); + ret = demo_load_shader(&cxt->demo, L"triangle.hlsl", "ps_main", "ps_5_0", "triangle.frag.spv", ps); + assert(ret); +} + +static void cxt_fence_create(struct cxt_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; + fence->event = demo_create_event(); + assert(fence->event); +} + +static void cxt_load_assets(struct cx_triangle *cxt) +{ + static const D3D12_INPUT_ELEMENT_DESC il_desc[] = + { + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + }; + + const struct + { + struct demo_vec3 position; + struct demo_vec4 colour; + } + vertices[] = + { + {{ 0.0f, 0.25f * cxt->aspect_ratio, 0.0f}, {1.0f, 0.0f, 0.0f, 1.0f}}, + {{ 0.25f, -0.25f * cxt->aspect_ratio, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}}, + {{-0.25f, -0.25f * cxt->aspect_ratio, 0.0f}, {0.0f, 0.0f, 1.0f, 1.0f}}, + }; + + D3D12_ROOT_SIGNATURE_DESC root_signature_desc; + D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc; + D3D12_RESOURCE_DESC resource_desc; + D3D12_HEAP_PROPERTIES heap_desc; + D3D12_RANGE read_range = {0, 0}; + HRESULT hr; + void *data; + + memset(&root_signature_desc, 0, sizeof(root_signature_desc)); + root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + hr = demo_create_root_signature(cxt->device, &root_signature_desc, &cxt->root_signature); + 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 = cxt->root_signature; + cxt_load_shaders(cxt, &pso_desc.VS, &pso_desc.PS); + demo_rasterizer_desc_init_default(&pso_desc.RasterizerState); + demo_blend_desc_init_default(&pso_desc.BlendState); + pso_desc.DepthStencilState.DepthEnable = FALSE; + pso_desc.DepthStencilState.StencilEnable = FALSE; + pso_desc.SampleMask = UINT_MAX; + pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + pso_desc.NumRenderTargets = 1; + pso_desc.RTVFormats[0] = DXGI_FORMAT_B8G8R8A8_UNORM; + pso_desc.SampleDesc.Count = 1; + hr = ID3D12Device_CreateGraphicsPipelineState(cxt->device, &pso_desc, + &IID_ID3D12PipelineState, (void **)&cxt->pipeline_state); + assert(SUCCEEDED(hr)); + + free((void *)pso_desc.PS.pShaderBytecode); + free((void *)pso_desc.VS.pShaderBytecode); + + hr = ID3D12Device_CreateCommandList(cxt->device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, cxt->command_allocator, + cxt->pipeline_state, &IID_ID3D12GraphicsCommandList, (void **)&cxt->command_list); + assert(SUCCEEDED(hr)); + + hr = ID3D12GraphicsCommandList_Close(cxt->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(vertices); + 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(cxt->device, &heap_desc, D3D12_HEAP_FLAG_NONE, &resource_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &IID_ID3D12Resource, (void **)&cxt->vb); + assert(SUCCEEDED(hr)); + + hr = ID3D12Resource_Map(cxt->vb, 0, &read_range, &data); + assert(SUCCEEDED(hr)); + memcpy(data, vertices, sizeof(vertices)); + ID3D12Resource_Unmap(cxt->vb, 0, NULL); + + cxt->vbv.BufferLocation = ID3D12Resource_GetGPUVirtualAddress(cxt->vb); + cxt->vbv.StrideInBytes = sizeof(*vertices); + cxt->vbv.SizeInBytes = sizeof(vertices); + + cxt_fence_create(&cxt->fence, cxt->device); + cxt_wait_for_previous_frame(cxt); +} + +static int cxt_main(void) +{ + unsigned int width = 640, height = 480; + struct cx_triangle cxt; + + memset(&cxt, 0, sizeof(cxt)); + + if (!demo_init(&cxt.demo)) + return EXIT_FAILURE; + + cxt.window = demo_window_create(&cxt.demo, "Vkd3d Triangle", + width, height, cxt_render_frame, &cxt); + + cxt.width = width; + cxt.height = height; + cxt.aspect_ratio = (float)width / (float)height; + + cxt.vp.Width = (float)width; + cxt.vp.Height = (float)height; + cxt.vp.MaxDepth = 1.0f; + + cxt.scissor_rect.right = width; + cxt.scissor_rect.bottom = height; + + cxt_load_pipeline(&cxt); + cxt_load_assets(&cxt); + + demo_process_events(&cxt.demo); + + cxt_wait_for_previous_frame(&cxt); + cxt_destroy_assets(&cxt); + cxt_destroy_pipeline(&cxt); + demo_cleanup(&cxt.demo); + + return EXIT_SUCCESS; +} + +#ifdef _WIN32 +int wmain(void) +#else +int main(void) +#endif +{ + return cxt_main(); +} diff --git a/demos/triangle.frag b/demos/triangle.frag new file mode 100644 index 00000000..aa4bfed5 --- /dev/null +++ b/demos/triangle.frag @@ -0,0 +1,10 @@ +#version 150 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec4 colour_in; +layout(location = 0) out vec4 colour_out; + +void main(void) +{ + colour_out = colour_in; +} diff --git a/demos/triangle.hlsl b/demos/triangle.hlsl new file mode 100644 index 00000000..90dd5ffe --- /dev/null +++ b/demos/triangle.hlsl @@ -0,0 +1,20 @@ +struct ps_in +{ + float4 position : SV_POSITION; + float4 colour : COLOR; +}; + +struct ps_in vs_main(float4 position : POSITION, float4 colour : COLOR) +{ + struct ps_in o; + + o.position = position; + o.colour = colour; + + return o; +} + +float4 ps_main(struct ps_in i) : SV_TARGET +{ + return i.colour; +} diff --git a/demos/triangle.vert b/demos/triangle.vert new file mode 100644 index 00000000..356464d6 --- /dev/null +++ b/demos/triangle.vert @@ -0,0 +1,14 @@ +#version 150 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec4 position_in; +layout(location = 1) in vec4 colour_in; + +layout(location = 0) out vec4 colour_out; + +void main(void) +{ + gl_Position.xzw = position_in.xzw; + gl_Position.y = -position_in.y; + colour_out = colour_in; +} diff --git a/include/dxgi.idl b/include/dxgi.idl index 6b73e81b..05e65271 100644 --- a/include/dxgi.idl +++ b/include/dxgi.idl @@ -25,6 +25,14 @@ import "dxgitype.idl"; #include "unknown.idl" +typedef enum DXGI_SWAP_EFFECT +{ + DXGI_SWAP_EFFECT_DISCARD = 0x0, + DXGI_SWAP_EFFECT_SEQUENTIAL = 0x1, + DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL = 0x3, + DXGI_SWAP_EFFECT_FLIP_DISCARD = 0x4, +} DXGI_SWAP_EFFECT; + typedef enum DXGI_MODE_ROTATION { DXGI_MODE_ROTATION_UNSPECIFIED = 0x0, @@ -40,6 +48,16 @@ interface IDXGIOutput; typedef struct DXGI_SWAP_CHAIN_DESC DXGI_SWAP_CHAIN_DESC; typedef struct DXGI_FRAME_STATISTICS DXGI_FRAME_STATISTICS; +typedef UINT DXGI_USAGE; + +const DXGI_USAGE DXGI_USAGE_SHADER_INPUT = 0x00000010ul; +const DXGI_USAGE DXGI_USAGE_RENDER_TARGET_OUTPUT = 0x00000020ul; +const DXGI_USAGE DXGI_USAGE_BACK_BUFFER = 0x00000040ul; +const DXGI_USAGE DXGI_USAGE_SHARED = 0x00000080ul; +const DXGI_USAGE DXGI_USAGE_READ_ONLY = 0x00000100ul; +const DXGI_USAGE DXGI_USAGE_DISCARD_ON_PRESENT = 0x00000200ul; +const DXGI_USAGE DXGI_USAGE_UNORDERED_ACCESS = 0x00000400ul; + [ local, object, diff --git a/include/dxgi1_2.idl b/include/dxgi1_2.idl index 7ff790f3..8329e4c7 100644 --- a/include/dxgi1_2.idl +++ b/include/dxgi1_2.idl @@ -23,7 +23,38 @@ import "dxgi.idl"; -typedef struct DXGI_SWAP_CHAIN_DESC1 DXGI_SWAP_CHAIN_DESC1; +typedef enum DXGI_SCALING +{ + DXGI_SCALING_STRETCH = 0x0, + DXGI_SCALING_NONE = 0x1, + DXGI_SCALING_ASPECT_RATIO_STRETCH = 0x2, + DXGI_SCALING_FORCE_DWORD = 0xffffffff, +} DXGI_SCALING; + +typedef enum DXGI_ALPHA_MODE +{ + DXGI_ALPHA_MODE_UNSPECIFIED = 0x0, + DXGI_ALPHA_MODE_PREMULTIPLIED = 0x1, + DXGI_ALPHA_MODE_STRAIGHT = 0x2, + DXGI_ALPHA_MODE_IGNORE = 0x3, + DXGI_ALPHA_MODE_FORCE_DWORD = 0xffffffff, +} DXGI_ALPHA_MODE; + +typedef struct DXGI_SWAP_CHAIN_DESC1 +{ + UINT Width; + UINT Height; + DXGI_FORMAT Format; + BOOL Stereo; + DXGI_SAMPLE_DESC SampleDesc; + DXGI_USAGE BufferUsage; + UINT BufferCount; + DXGI_SCALING Scaling; + DXGI_SWAP_EFFECT SwapEffect; + DXGI_ALPHA_MODE AlphaMode; + UINT Flags; +} DXGI_SWAP_CHAIN_DESC1; + typedef struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC DXGI_SWAP_CHAIN_FULLSCREEN_DESC; typedef struct DXGI_PRESENT_PARAMETERS DXGI_PRESENT_PARAMETERS; diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c index 8b4ef653..7008d3a5 100644 --- a/libs/vkd3d/device.c +++ b/libs/vkd3d/device.c @@ -24,6 +24,12 @@ static HRESULT vkd3d_instance_init(struct vkd3d_instance *instance) { + static const char * const extensions[] = + { + "VK_KHR_surface", + "VK_KHR_xcb_surface", + }; + VkApplicationInfo application_info; VkInstanceCreateInfo instance_info; VkInstance vk_instance; @@ -46,8 +52,8 @@ static HRESULT vkd3d_instance_init(struct vkd3d_instance *instance) instance_info.pApplicationInfo = &application_info; instance_info.enabledLayerCount = 0; instance_info.ppEnabledLayerNames = NULL; - instance_info.enabledExtensionCount = 0; - instance_info.ppEnabledExtensionNames = NULL; + instance_info.enabledExtensionCount = ARRAY_SIZE(extensions); + instance_info.ppEnabledExtensionNames = extensions; if ((vr = vkCreateInstance(&instance_info, NULL, &vk_instance))) { @@ -351,6 +357,11 @@ static HRESULT vkd3d_select_physical_device(struct vkd3d_instance *instance, static HRESULT vkd3d_create_vk_device(struct d3d12_device *device) { + static const char * const extensions[] = + { + "VK_KHR_swapchain", + }; + const struct vkd3d_vk_instance_procs *vk_procs = &device->vkd3d_instance.vk_procs; unsigned int direct_queue_family_index, copy_queue_family_index; VkQueueFamilyProperties *queue_properties; @@ -434,8 +445,8 @@ static HRESULT vkd3d_create_vk_device(struct d3d12_device *device) device_info.pQueueCreateInfos = queue_info; device_info.enabledLayerCount = 0; device_info.ppEnabledLayerNames = NULL; - device_info.enabledExtensionCount = 0; - device_info.ppEnabledExtensionNames = NULL; + device_info.enabledExtensionCount = ARRAY_SIZE(extensions); + device_info.ppEnabledExtensionNames = extensions; device_info.pEnabledFeatures = &device_features; vr = VK_CALL(vkCreateDevice(physical_device, &device_info, NULL, &vk_device));