From 84c6a1a8cf5babf18536456b7d5f4d8b74557e17 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 30 Apr 2024 15:41:34 -0400 Subject: [PATCH] Add a shader tool This tool will do what is necessary to populate the Vulkan shader cache with most of the shaders that GTK will need. --- tools/gtk-shader-tool-compile.c | 315 ++++++++++++++++++++++++++++++++ tools/gtk-shader-tool.c | 113 ++++++++++++ tools/gtk-shader-tool.h | 3 + tools/meson.build | 9 +- 4 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 tools/gtk-shader-tool-compile.c create mode 100644 tools/gtk-shader-tool.c create mode 100644 tools/gtk-shader-tool.h diff --git a/tools/gtk-shader-tool-compile.c b/tools/gtk-shader-tool-compile.c new file mode 100644 index 0000000000..a78aab41b4 --- /dev/null +++ b/tools/gtk-shader-tool-compile.c @@ -0,0 +1,315 @@ +/* Copyright 2024 Red Hat, Inc. + * + * GTK+ 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 of the + * License, or (at your option) any later version. + * + * GLib 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 GTK+; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "gdk/gdkvulkancontextprivate.h" +#include "gsk/gpu/gskgputypesprivate.h" +#include "gsk/gpu/gskvulkandeviceprivate.h" +#include "gsk/gpu/gskgpushaderopprivate.h" + +#include "gtk-shader-tool.h" + +static gboolean verbose; + +typedef struct { + const GskGpuShaderOpClass *op_class; + unsigned int variation; + GskGpuShaderClip clip; + GskGpuBlend blend; + VkFormat format; + + gsize n_buffers; + gsize n_samplers; + gsize n_immutable_samplers; +} PipelineData; + +static const char *clip_name[] = { "NONE", "RECT", "ROUNDED" }; +static const char *blend_name[] = { "OVER", "ADD", "CLEAR" }; + +static gboolean +parse_line (const char *line, + PipelineData *data) +{ + char *p, *q; + char *name; + char *parms; + char **split; + int i; + + if (!g_str_has_prefix (line, "Create Vulkan pipeline (")) + return FALSE; + + line += strlen ("Create Vulkan pipeline ("); + + p = strchr (line, ' '); + if (!p) + return FALSE; + + name = g_strndup (line, p - line); + data->op_class = gsk_gpu_shader_op_class_from_name (name); + if (!data->op_class) + { + g_printerr ("%s\n", g_strdup_printf (_("No such shader: %s"), name)); + return FALSE; + } + g_free (name); + p++; + + p = strchr (p, ' '); + if (!p) + return FALSE; + + q = strstr (p, ") for layout ("); + if (!q) + return FALSE; + + parms = g_strndup (p, q - p); + split = g_strsplit (parms, "/", 0); + g_free (parms); + + if (g_strv_length (split) != 4) + { + g_strfreev (split); + return FALSE; + } + + data->variation = (unsigned int) g_ascii_strtoull (split[0], NULL, 10); + + for (i = 0; i < G_N_ELEMENTS (clip_name); i++) + { + if (strcmp (split[1], clip_name[i]) == 0) + { + data->clip = i; + break; + } + } + if (i == G_N_ELEMENTS (clip_name)) + { + g_printerr ("%s\n", g_strdup_printf (_("No such clip: %s"), split[1])); + g_strfreev (split); + return FALSE; + } + + for (i = 0; i < G_N_ELEMENTS (blend_name); i++) + { + if (strcmp (split[2], blend_name[i]) == 0) + { + data->blend = i; + break; + } + } + if (i == G_N_ELEMENTS (blend_name)) + { + g_printerr ("%s\n", g_strdup_printf (_("No such blend: %s"), split[2])); + g_strfreev (split); + return FALSE; + } + + data->format = (guint) g_ascii_strtoull (split[3], NULL, 10); + + g_strfreev (split); + + p = q + strlen (") for layout ("); + q = strstr (p, ")"); + if (!q) + return FALSE; + + parms = g_strndup (p, q - p); + split = g_strsplit (parms, "/", 0); + g_free (parms); + + if (g_strv_length (split ) != 3) + { + g_strfreev (split); + return FALSE; + } + + data->n_buffers = (gsize) g_ascii_strtoull (split[0], NULL, 10); + data->n_samplers = (gsize) g_ascii_strtoull (split[1], NULL, 10); + data->n_immutable_samplers = (gsize) g_ascii_strtoull (split[2], NULL, 10); + + g_strfreev (split); + + return TRUE; +} + +static gboolean +compile_shader (GskVulkanDevice *device, + PipelineData *data) +{ + GskVulkanPipelineLayout *layout; + VkRenderPass render_pass; + VkPipeline pipeline; + + if (data->n_immutable_samplers != 0) + { + g_printerr ("%s\n", _("Can't handle pipeline layouts with immutable samplers\n")); + return FALSE; + } + + if (verbose) + { + g_print ("layout: %3lu buffers %3lu samplers ", + data->n_buffers, data->n_samplers); + g_print ("shader: %20s, variation %u clip %7s blend %5s format %u\n", + data->op_class->shader_name, + data->variation, + clip_name[data->clip], + blend_name[data->blend], + data->format); + } + + layout = gsk_vulkan_device_acquire_pipeline_layout (device, + NULL, + data->n_immutable_samplers, + data->n_samplers, + data->n_buffers); + + render_pass = gsk_vulkan_device_get_vk_render_pass (device, + data->format, + VK_IMAGE_LAYOUT_PREINITIALIZED, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + pipeline = gsk_vulkan_device_get_vk_pipeline (device, + layout, + data->op_class, + data->variation, + data->clip, + data->blend, + data->format, + render_pass); + + gsk_vulkan_device_release_pipeline_layout (device, layout); + + return pipeline != VK_NULL_HANDLE; +} + +static void +compile_shaders_from_file (GskVulkanDevice *device, + const char *filename) +{ + char *buffer; + gsize size; + GError *error = NULL; + char **lines; + PipelineData data; + + if (!g_file_get_contents (filename, &buffer, &size, &error)) + { + char *msg = g_strdup_printf (_("Failed to read %s"), filename); + g_printerr ("%s: %s\n", msg, error->message); + exit (1); + } + + lines = g_strsplit (buffer, "\n", 0); + + for (int i = 0; lines[i]; i++) + { + lines[i] = g_strstrip (lines[i]); + + if (strlen (lines[i]) == 0) + continue; + + if (!parse_line (lines[i], &data)) + { + char *msg = g_strdup_printf (_("Could not parse line %d: %s"), i + 1, lines[i]); + g_printerr ("%s\n", msg); + exit (1); + } + + if (!compile_shader (device, &data)) + { + char *msg = g_strdup_printf (_("Failed to compile shader for line %d: %s"), i + 1, lines[i]); + g_printerr ("%s\n", msg); + exit (1); + } + } + + g_strfreev (lines); + g_free (buffer); +} + +void +do_compile (int *argc, + const char ***argv) +{ + GError *error = NULL; + char **args = NULL; + GOptionContext *context; + const GOptionEntry entries[] = { + { "verbose", 0, 0, G_OPTION_ARG_NONE, &verbose, N_("Be verbose"), NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, NULL, N_("FILE") }, + { NULL, } + }; + GdkDisplay *display; + GskGpuDevice *device; + + g_set_prgname ("gtk4-shader-tool compile"); + context = g_option_context_new (NULL); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_set_summary (context, _("Compile shaders.")); + + if (!g_option_context_parse (context, argc, (char ***)argv, &error)) + { + g_printerr ("%s\n", error->message); + g_error_free (error); + exit (1); + } + + g_option_context_free (context); + + if (args == NULL) + { + g_printerr ("%s\n", _("No file specified.")); + exit (1); + } + + display = gdk_display_get_default (); + device = gsk_vulkan_device_get_for_display (display, &error); + if (device == NULL) + { + g_printerr ("%s: %s\n", _("Failed to get Vulkan device"), error->message); + exit (1); + } + + for (int i = 0; args[i]; i++) + compile_shaders_from_file (GSK_VULKAN_DEVICE (device), args[i]); + + if (gdk_display_vulkan_pipeline_cache_save (display)) + { + GFile *file = gdk_display_vulkan_pipeline_cache_file (display); + + g_print (_("Pipeline cache in %s\n"), g_file_peek_path (file)); + + g_object_unref (file); + } + + g_strfreev (args); +} diff --git a/tools/gtk-shader-tool.c b/tools/gtk-shader-tool.c new file mode 100644 index 0000000000..631045361d --- /dev/null +++ b/tools/gtk-shader-tool.c @@ -0,0 +1,113 @@ +/* Copyright 2023 Red Hat, Inc. + * + * GTK 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 of the + * License, or (at your option) any later version. + * + * GTK 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 GTK; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "gtk-shader-tool.h" + +static void G_GNUC_NORETURN +usage (void) +{ + g_print (_("Usage:\n" + " gtk4-shader-tool [COMMAND] [OPTION…] FILE\n" + "\n" + "Perform various tasks on GTK shaders.\n" + "\n" + "Commands:\n" + " compile Compile Vulkan pipelines\n" + "\n")); + exit (1); +} + +static GLogWriterOutput +log_writer_func (GLogLevelFlags level, + const GLogField *fields, + gsize n_fields, + gpointer user_data) +{ + gsize i; + const char *domain = NULL; + const char *message = NULL; + + for (i = 0; i < n_fields; i++) + { + if (g_strcmp0 (fields[i].key, "GLIB_DOMAIN") == 0) + domain = fields[i].value; + else if (g_strcmp0 (fields[i].key, "MESSAGE") == 0) + message = fields[i].value; + } + + if (message != NULL && !g_log_writer_default_would_drop (level, domain)) + { + const char *prefix; + switch (level & G_LOG_LEVEL_MASK) + { + case G_LOG_LEVEL_ERROR: + prefix = "ERROR"; + break; + case G_LOG_LEVEL_CRITICAL: + prefix = "CRITICAL"; + break; + case G_LOG_LEVEL_WARNING: + prefix = "WARNING"; + break; + default: + prefix = "INFO"; + break; + } + g_printerr ("%s-%s: %s\n", domain, prefix, message); + } + + return G_LOG_WRITER_HANDLED; +} + +int +main (int argc, const char *argv[]) +{ + g_set_prgname ("gtk-shader-tool"); + + g_log_set_writer_func (log_writer_func, NULL, NULL); + + gtk_init (); + + if (argc < 2) + usage (); + + if (strcmp (argv[1], "--help") == 0) + usage (); + + argv++; + argc--; + + if (strcmp (argv[0], "compile") == 0) + do_compile (&argc, &argv); + else + usage (); + + return 0; +} diff --git a/tools/gtk-shader-tool.h b/tools/gtk-shader-tool.h new file mode 100644 index 0000000000..79beab0080 --- /dev/null +++ b/tools/gtk-shader-tool.h @@ -0,0 +1,3 @@ +#pragma once + +void do_compile (int *argc, const char ***argv); diff --git a/tools/meson.build b/tools/meson.build index f259cc5070..2312999dad 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -52,6 +52,13 @@ gtk_tools = [ ['gtk4-encode-symbolic-svg', ['encodesymbolic.c'], [ libgtk_static_dep ] ], ] +if vulkan_dep.found() + gtk_tools += [[ 'gtk4-shader-tool', + [ 'gtk-shader-tool.c', + 'gtk-shader-tool-compile.c' ], + [ libgtk_static_dep ]]] +endif + if os_unix gtk_tools += [['gtk4-launch', ['gtk-launch.c'], [libgtk_dep]]] endif @@ -64,7 +71,7 @@ foreach tool: gtk_tools exe = executable(tool_name, sources: tool_srcs, include_directories: [confinc], - c_args: common_cflags + [ '-DBUILD_TOOLS' ], + c_args: common_cflags + [ '-DBUILD_TOOLS', '-I../gsk' ], dependencies: tool_deps, install: true, )