diff --git a/tests/meson.build b/tests/meson.build index 32f936b8dc..76944d72b1 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -137,6 +137,12 @@ if os_linux ] endif +if os_win32 + gtk_tests += [ + ['testd3d12', ['testd3d12.c', 'gtkclipper.c']], + ] +endif + if x11_enabled gtk_tests += [['testerrors']] endif @@ -153,5 +159,5 @@ foreach t: gtk_tests include_directories: [confinc, gdkinc], c_args: test_args + common_cflags, dependencies: [libgtk_dep, libm], - ) + ) endforeach diff --git a/tests/testd3d12.c b/tests/testd3d12.c new file mode 100644 index 0000000000..392498d40d --- /dev/null +++ b/tests/testd3d12.c @@ -0,0 +1,432 @@ +#include + +#include + +#include "gtkclipperprivate.h" + +static char * +supported_formats_to_string (void) +{ + return g_strdup ("whatever"); +} + +static GdkTexture * +make_d3d12_texture (ID3D12Device *device, + GdkTexture *texture, + guint32 format, + gboolean disjoint, + gboolean premultiplied, + gboolean flip) +{ + GdkTextureDownloader *downloader; + UINT64 buffer_size; + GdkD3D12TextureBuilder *builder; + GError *error = NULL; + HRESULT hr; + ID3D12CommandAllocator *allocator; + ID3D12GraphicsCommandList *commands; + ID3D12CommandQueue *queue; + ID3D12Resource *buffer, *resource; + D3D12_RESOURCE_DESC resource_desc; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; + void *buffer_data; + + hr = ID3D12Device_CreateCommittedResource (device, + (&(D3D12_HEAP_PROPERTIES) { + .Type = D3D12_HEAP_TYPE_DEFAULT, + .CreationNodeMask = 1, + .VisibleNodeMask = 1, + }), + D3D12_HEAP_FLAG_SHARED, + (&(D3D12_RESOURCE_DESC) { + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Width = gdk_texture_get_width (texture), + .Height = gdk_texture_get_height (texture), + .DepthOrArraySize = 1, + .MipLevels = 0, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .SampleDesc = { + .Count = 1, + .Quality = 0, + }, + .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, + .Flags = D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS, + }), + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + &IID_ID3D12Resource, + (void **) &resource); + g_assert (SUCCEEDED (hr)); + + ID3D12Resource_GetDesc (resource, &resource_desc); + ID3D12Device_GetCopyableFootprints (device, + &resource_desc, + 0, 1, 0, + &footprint, + NULL, + NULL, + NULL); + buffer_size = footprint.Footprint.RowPitch * footprint.Footprint.Height; + + hr = ID3D12Device_CreateCommittedResource (device, + (&(D3D12_HEAP_PROPERTIES) { + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CreationNodeMask = 1, + .VisibleNodeMask = 1, + }), + D3D12_HEAP_FLAG_NONE, + (&(D3D12_RESOURCE_DESC) { + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Width = buffer_size, + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .SampleDesc = { + .Count = 1, + .Quality = 0, + }, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + }), + D3D12_RESOURCE_STATE_GENERIC_READ, + NULL, + &IID_ID3D12Resource, + (void **) &buffer); + g_assert (SUCCEEDED (hr)); + + ID3D12Resource_Map (buffer, 0, (&(D3D12_RANGE) { 0, buffer_size }), &buffer_data ); + + downloader = gdk_texture_downloader_new (texture); + if (premultiplied) + gdk_texture_downloader_set_format (downloader, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED); + else + gdk_texture_downloader_set_format (downloader, GDK_MEMORY_R8G8B8A8); + gdk_texture_downloader_download_into (downloader, buffer_data, footprint.Footprint.RowPitch); + gdk_texture_downloader_free (downloader); + + if (flip) + { + int x, y, width, height, stride; + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + stride = footprint.Footprint.RowPitch; + for (y = 0; y < height; y++) + { + guint32 *row = (guint32 *) ((guint8 *) buffer_data + y * stride); + for (x = 0; x < width / 2; x++) + { + guint32 p = row[x]; + row[x] = row[width - 1 - x]; + row[width - 1 - x] = p; + } + } + } + + ID3D12Resource_Unmap (buffer, 0, (&(D3D12_RANGE) { 0, buffer_size })); + + hr = ID3D12Device_CreateCommandAllocator (device, + D3D12_COMMAND_LIST_TYPE_DIRECT, + &IID_ID3D12CommandAllocator, + (void **) &allocator); + g_assert (SUCCEEDED (hr)); + + hr = ID3D12Device_CreateCommandList (device, + 0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + allocator, + NULL, + &IID_ID3D12GraphicsCommandList, + (void **) &commands); + g_assert (SUCCEEDED (hr)); + + ID3D12GraphicsCommandList_CopyTextureRegion (commands, + (&(D3D12_TEXTURE_COPY_LOCATION) { + .pResource = resource, + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = 0, + }), + 0, 0, 0, + (&(D3D12_TEXTURE_COPY_LOCATION) { + .pResource = buffer, + .Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, + .PlacedFootprint = footprint, + }), + NULL); + hr = ID3D12GraphicsCommandList_Close (commands); + g_assert (SUCCEEDED (hr)); + + hr = ID3D12Device_CreateCommandQueue (device, + (&(D3D12_COMMAND_QUEUE_DESC) { + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + }), + &IID_ID3D12CommandQueue, + (void **) &queue); + g_assert (SUCCEEDED (hr)); + + ID3D12CommandQueue_ExecuteCommandLists (queue, 1, (ID3D12CommandList **) &commands); + + builder = gdk_d3d12_texture_builder_new (); + + gdk_d3d12_texture_builder_set_resource (builder, resource); + gdk_d3d12_texture_builder_set_premultiplied (builder, premultiplied); + + texture = gdk_d3d12_texture_builder_build (builder, NULL, NULL, &error); + if (!texture) + g_error ("Failed to create d3d12 texture: %s", error->message); + + g_object_unref (builder); + ID3D12Resource_Release (buffer); + ID3D12Resource_Release (resource); + ID3D12GraphicsCommandList_Release (commands); + ID3D12CommandAllocator_Release (allocator); + ID3D12CommandQueue_Release (queue); + + return texture; +} + +G_GNUC_NORETURN +static void +usage (void) +{ + char *formats = supported_formats_to_string (); + g_print ("Usage: testdmabuf [--undecorated][--disjoint][--download-to FILE][--padding PADDING] FORMAT FILE\n" + "Supported formats: %s\n", formats); + g_free (formats); + exit (1); +} + +static gboolean +toggle_fullscreen (GtkWidget *widget, + GVariant *args, + gpointer data) +{ + GtkWindow *window = GTK_WINDOW (widget); + + if (gtk_window_is_fullscreen (window)) + gtk_window_unfullscreen (window); + else + gtk_window_fullscreen (window); + + return TRUE; +} + +static gboolean +toggle_overlay (GtkWidget *widget, + GVariant *args, + gpointer data) +{ + static GtkWidget *child = NULL; + GtkOverlay *overlay = (GtkOverlay *) data; + + if (child) + { + gtk_overlay_remove_overlay (overlay, child); + child = NULL; + } + else + { + GtkWidget *spinner; + spinner = gtk_spinner_new (); + gtk_spinner_start (GTK_SPINNER (spinner)); + child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE); + gtk_box_append (GTK_BOX (child), spinner); + gtk_box_append (GTK_BOX (child), gtk_image_new_from_icon_name ("media-playback-start-symbolic")); + gtk_widget_set_halign (child, GTK_ALIGN_CENTER); + gtk_widget_set_valign (child, GTK_ALIGN_CENTER); + gtk_overlay_add_overlay (overlay, child); + } + + return TRUE; +} + +static GdkTexture *texture; +static GdkTexture *texture_flipped; + +static gboolean +toggle_flip (GtkWidget *widget, + GVariant *args, + gpointer data) +{ + GtkPicture *picture = (GtkPicture *) data; + + if (!texture_flipped) + return FALSE; + + if (gtk_picture_get_paintable (picture) == GDK_PAINTABLE (texture)) + gtk_picture_set_paintable (picture, GDK_PAINTABLE (texture_flipped)); + else + gtk_picture_set_paintable (picture, GDK_PAINTABLE (texture)); + + return TRUE; +} + +static gboolean +toggle_start (GtkWidget *widget, + GVariant *args, + gpointer data) +{ + GtkWidget *offload = (GtkWidget *) data; + + if (gtk_widget_get_halign (offload) == GTK_ALIGN_CENTER) + gtk_widget_set_halign (offload, GTK_ALIGN_START); + else + gtk_widget_set_halign (offload, GTK_ALIGN_CENTER); + + return TRUE; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window, *offload, *picture, *overlay; + char *filename; + guint32 format; + gboolean disjoint = FALSE; + gboolean premultiplied = TRUE; + gboolean decorated = TRUE; + gboolean fullscreen = FALSE; + unsigned int i; + const char *save_filename = NULL; + GtkEventController *controller; + GtkShortcutTrigger *trigger; + GtkShortcutAction *action; + GtkShortcut *shortcut; + GdkPaintable *paintable; + GdkTexture *orig; + ID3D12Device *device; + HRESULT hr; + int padding[4] = { 0, }; /* left, right, top, bottom */ + int padding_set = 0; + + for (i = 1; i < argc; i++) + { + if (g_str_equal (argv[i], "--disjoint")) + disjoint = TRUE; + else if (g_str_equal (argv[i], "--undecorated")) + decorated = FALSE; + else if (g_str_equal (argv[i], "--fullscreen")) + fullscreen = TRUE; + else if (g_str_equal (argv[i], "--unpremultiplied")) + premultiplied = FALSE; + else if (g_str_equal (argv[i], "--download-to")) + { + i++; + if (i == argc) + usage (); + + save_filename = argv[i]; + } + else if (g_str_equal (argv[i], "--padding")) + { + if (padding_set < 4) + { + char **strv; + + i++; + if (i == argc) + usage (); + + strv = g_strsplit (argv[i], ",", 0); + if (g_strv_length (strv) > 4) + g_error ("Too much padding"); + + for (padding_set = 0; padding_set < 4; padding_set++) + { + guint64 num; + GError *error = NULL; + + if (!strv[padding_set]) + break; + + if (!g_ascii_string_to_unsigned (strv[padding_set], 10, 0, 100, &num, &error)) + g_error ("%s", error->message); + + padding[padding_set] = (int) num; + } + } + else + g_error ("Too much padding"); + } + else + break; + } + + if (argc - i != 2) + { + usage (); + return 1; + } + + filename = argv[argc - 1]; + + gtk_init (); + + hr = D3D12CreateDevice (NULL, D3D_FEATURE_LEVEL_12_0, &IID_ID3D12Device, (void **) &device); + g_assert (SUCCEEDED (hr)); + + orig = gdk_texture_new_from_filename (filename, NULL); + format = strtoul (argv[argc-2], NULL, 10); + texture = make_d3d12_texture (device, orig, format, disjoint, premultiplied, FALSE); + texture_flipped = make_d3d12_texture (device, orig, format, disjoint, premultiplied, TRUE); + g_object_unref (orig); + + if (padding_set > 0) + { + paintable = gtk_clipper_new (GDK_PAINTABLE (texture), + &GRAPHENE_RECT_INIT (padding[0], + padding[2], + gdk_texture_get_width (texture) - padding[0] - padding[1], + gdk_texture_get_height (texture) - padding[2] - padding[3])); + } + else + paintable = GDK_PAINTABLE (texture); + + if (save_filename) + gdk_texture_save_to_png (texture, save_filename); + + window = gtk_window_new (); + gtk_window_set_decorated (GTK_WINDOW (window), decorated); + if (fullscreen) + gtk_window_fullscreen (GTK_WINDOW (window)); + + picture = gtk_picture_new_for_paintable (paintable); + offload = gtk_graphics_offload_new (picture); + gtk_widget_set_halign (offload, GTK_ALIGN_CENTER); + gtk_widget_set_valign (offload, GTK_ALIGN_CENTER); + overlay = gtk_overlay_new (); + + gtk_overlay_set_child (GTK_OVERLAY (overlay), offload); + gtk_window_set_child (GTK_WINDOW (window), overlay); + + controller = gtk_shortcut_controller_new (); + + trigger = gtk_keyval_trigger_new (GDK_KEY_F11, GDK_NO_MODIFIER_MASK); + action = gtk_callback_action_new (toggle_fullscreen, NULL, NULL); + shortcut = gtk_shortcut_new (trigger, action); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut); + + trigger = gtk_keyval_trigger_new (GDK_KEY_O, GDK_CONTROL_MASK); + action = gtk_callback_action_new (toggle_overlay, overlay, NULL); + shortcut = gtk_shortcut_new (trigger, action); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut); + + trigger = gtk_keyval_trigger_new (GDK_KEY_F, GDK_CONTROL_MASK); + action = gtk_callback_action_new (toggle_flip, picture, NULL); + shortcut = gtk_shortcut_new (trigger, action); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut); + + trigger = gtk_keyval_trigger_new (GDK_KEY_S, GDK_CONTROL_MASK); + action = gtk_callback_action_new (toggle_start, offload, NULL); + shortcut = gtk_shortcut_new (trigger, action); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut); + + gtk_widget_add_controller (window, controller); + + gtk_window_present (GTK_WINDOW (window)); + + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) + g_main_context_iteration (NULL, TRUE); + + return 0; +}