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.
This commit is contained in:
Matthias Clasen
2024-04-30 15:41:34 -04:00
parent 57f3598b71
commit 84c6a1a8cf
4 changed files with 439 additions and 1 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#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);
}

113
tools/gtk-shader-tool.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#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;
}

3
tools/gtk-shader-tool.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
void do_compile (int *argc, const char ***argv);

View File

@@ -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,
)