mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 14:10:30 +00:00
0092a08dfc
This adds a small demo of using OpenGL shaders, it renders a quad over the entire widget with a custom fragment shader. The coordinates and the uniform names are compatible with the ones on shadertoy.com (although some features, like texture inputs are missing currently). The default shader in the demo is https://www.shadertoy.com/view/wsjBD3 which is CC0, so it is redistributable by Gtk+ (most other shaders are CC-BY-NC-SA which isn't obviously compatible). I also added a set of buttons loading a few other CC0 shaders I found.
525 lines
15 KiB
C
525 lines
15 KiB
C
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <epoxy/gl.h>
|
|
|
|
#include "gtkshadertoy.h"
|
|
|
|
const char *default_image_shader =
|
|
"void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n"
|
|
" // Normalized pixel coordinates (from 0 to 1)\n"
|
|
" vec2 uv = fragCoord/iResolution.xy;\n"
|
|
"\n"
|
|
" // Time varying pixel color\n"
|
|
" vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));\n"
|
|
"\n"
|
|
" if (distance(iMouse.xy, fragCoord.xy) <= 10.0) {\n"
|
|
" col = vec3(0.0);\n"
|
|
" }\n"
|
|
"\n"
|
|
" // Output to screen\n"
|
|
" fragColor = vec4(col,1.0);\n"
|
|
"}\n";
|
|
|
|
const char *shadertoy_vertex_shader =
|
|
"#version 150 core\n"
|
|
"\n"
|
|
"uniform vec3 iResolution;\n"
|
|
"\n"
|
|
"in vec2 position;\n"
|
|
"out vec2 fragCoord;\n"
|
|
"\n"
|
|
"void main() {\n"
|
|
" gl_Position = vec4(position, 0.0, 1.0);\n"
|
|
"\n"
|
|
" // Convert from OpenGL coordinate system (with origin in center\n"
|
|
" // of screen) to Shadertoy/texture coordinate system (with origin\n"
|
|
" // in lower left corner)\n"
|
|
" fragCoord = (gl_Position.xy + vec2(1.0)) / vec2(2.0) * iResolution.xy;\n"
|
|
"}\n";
|
|
|
|
const char *fragment_prefix =
|
|
"#version 150 core\n"
|
|
"\n"
|
|
"uniform vec3 iResolution; // viewport resolution (in pixels)\n"
|
|
"uniform float iTime; // shader playback time (in seconds)\n"
|
|
"uniform float iTimeDelta; // render time (in seconds)\n"
|
|
"uniform int iFrame; // shader playback frame\n"
|
|
"uniform float iChannelTime[4]; // channel playback time (in seconds)\n"
|
|
"uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)\n"
|
|
"uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click\n"
|
|
"uniform sampler2D iChannel0;\n"
|
|
"uniform sampler2D iChannel1;\n"
|
|
"uniform sampler2D iChannel2;\n"
|
|
"uniform sampler2D iChannel3;\n"
|
|
"uniform vec4 iDate; // (year, month, day, time in seconds)\n"
|
|
"uniform float iSampleRate; // sound sample rate (i.e., 44100)\n"
|
|
"\n"
|
|
"in vec2 fragCoord;\n"
|
|
"out vec4 fragColor;\n";
|
|
|
|
|
|
// Fragment shader suffix
|
|
const char *fragment_suffix =
|
|
" void main() {\n"
|
|
" mainImage(fragColor, fragCoord);\n"
|
|
" }\n";
|
|
|
|
typedef struct {
|
|
char *image_shader;
|
|
gboolean image_shader_dirty;
|
|
|
|
gboolean error_set;
|
|
|
|
/* Vertex buffers */
|
|
GLuint vao;
|
|
GLuint buffer;
|
|
|
|
/* Active program */
|
|
GLuint program;
|
|
|
|
/* Location of uniforms for program */
|
|
GLuint resolution_location;
|
|
GLuint time_location;
|
|
GLuint timedelta_location;
|
|
GLuint frame_location;
|
|
GLuint mouse_location;
|
|
|
|
/* Current uniform values */
|
|
float resolution[3];
|
|
float time;
|
|
float timedelta;
|
|
float mouse[4];
|
|
int frame;
|
|
|
|
/* Animation data */
|
|
gint64 first_frame_time;
|
|
gint64 first_frame;
|
|
guint tick;
|
|
} GtkShadertoyPrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GtkShadertoy, gtk_shadertoy, GTK_TYPE_GL_AREA)
|
|
|
|
static gboolean gtk_shadertoy_render (GtkGLArea *area,
|
|
GdkGLContext *context);
|
|
static void gtk_shadertoy_reshape (GtkGLArea *area,
|
|
int width,
|
|
int height);
|
|
static void gtk_shadertoy_realize (GtkWidget *widget);
|
|
static void gtk_shadertoy_unrealize (GtkWidget *widget);
|
|
static gboolean gtk_shadertoy_tick (GtkWidget *widget,
|
|
GdkFrameClock *frame_clock,
|
|
gpointer user_data);
|
|
|
|
GtkWidget *
|
|
gtk_shadertoy_new (void)
|
|
{
|
|
return g_object_new (gtk_shadertoy_get_type (), NULL);
|
|
}
|
|
|
|
static void
|
|
drag_begin_cb (GtkGestureDrag *drag,
|
|
double x,
|
|
double y,
|
|
gpointer user_data)
|
|
{
|
|
GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
int height = gtk_widget_get_height (GTK_WIDGET (shadertoy));
|
|
int scale = gtk_widget_get_scale_factor (GTK_WIDGET (shadertoy));
|
|
|
|
priv->mouse[0] = x * scale;
|
|
priv->mouse[1] = (height - y) * scale;
|
|
priv->mouse[2] = priv->mouse[0];
|
|
priv->mouse[3] = priv->mouse[1];
|
|
}
|
|
|
|
static void
|
|
drag_update_cb (GtkGestureDrag *drag,
|
|
double dx,
|
|
double dy,
|
|
gpointer user_data)
|
|
{
|
|
GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
int width = gtk_widget_get_width (GTK_WIDGET (shadertoy));
|
|
int height = gtk_widget_get_height (GTK_WIDGET (shadertoy));
|
|
int scale = gtk_widget_get_scale_factor (GTK_WIDGET (shadertoy));
|
|
double x, y;
|
|
|
|
gtk_gesture_drag_get_start_point (drag, &x, &y);
|
|
x += dx;
|
|
y += dy;
|
|
|
|
if (x >= 0 && x < width &&
|
|
y >= 0 && y < height)
|
|
{
|
|
priv->mouse[0] = x * scale;
|
|
priv->mouse[1] = (height - y) * scale;
|
|
}
|
|
}
|
|
|
|
static void
|
|
drag_end_cb (GtkGestureDrag *drag,
|
|
gdouble dx,
|
|
gdouble dy,
|
|
gpointer user_data)
|
|
{
|
|
GtkShadertoy *shadertoy = GTK_SHADERTOY (user_data);
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
|
|
priv->mouse[2] = -priv->mouse[2];
|
|
priv->mouse[3] = -priv->mouse[3];
|
|
}
|
|
|
|
static void
|
|
gtk_shadertoy_init (GtkShadertoy *shadertoy)
|
|
{
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
GtkGesture *drag;
|
|
|
|
priv->image_shader = g_strdup (default_image_shader);
|
|
priv->tick = gtk_widget_add_tick_callback (GTK_WIDGET (shadertoy), gtk_shadertoy_tick, shadertoy, NULL);
|
|
|
|
drag = gtk_gesture_drag_new ();
|
|
gtk_widget_add_controller (GTK_WIDGET (shadertoy), GTK_EVENT_CONTROLLER (drag));
|
|
g_signal_connect (drag, "drag-begin", (GCallback)drag_begin_cb, shadertoy);
|
|
g_signal_connect (drag, "drag-update", (GCallback)drag_update_cb, shadertoy);
|
|
g_signal_connect (drag, "drag-end", (GCallback)drag_end_cb, shadertoy);
|
|
}
|
|
|
|
static void
|
|
gtk_shadertoy_finalize (GObject *obj)
|
|
{
|
|
GtkShadertoy *shadertoy = GTK_SHADERTOY (obj);
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
|
|
gtk_widget_remove_tick_callback (GTK_WIDGET (shadertoy), priv->tick);
|
|
g_free (priv->image_shader);
|
|
|
|
G_OBJECT_CLASS (gtk_shadertoy_parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gtk_shadertoy_class_init (GtkShadertoyClass *klass)
|
|
{
|
|
GTK_GL_AREA_CLASS (klass)->render = gtk_shadertoy_render;
|
|
GTK_GL_AREA_CLASS (klass)->resize = gtk_shadertoy_reshape;
|
|
|
|
GTK_WIDGET_CLASS (klass)->realize = gtk_shadertoy_realize;
|
|
GTK_WIDGET_CLASS (klass)->unrealize = gtk_shadertoy_unrealize;
|
|
|
|
G_OBJECT_CLASS (klass)->finalize = gtk_shadertoy_finalize;
|
|
}
|
|
|
|
/* new window size or exposure */
|
|
static void
|
|
gtk_shadertoy_reshape (GtkGLArea *area, int width, int height)
|
|
{
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private ((GtkShadertoy *) area);
|
|
|
|
priv->resolution[0] = width;
|
|
priv->resolution[1] = height;
|
|
priv->resolution[2] = 1.0; /* screen aspect ratio */
|
|
|
|
/* Set the viewport */
|
|
glViewport (0, 0, (GLint) width, (GLint) height);
|
|
}
|
|
|
|
static GLuint
|
|
create_shader (int type,
|
|
const char *src,
|
|
GError **error)
|
|
{
|
|
GLuint shader;
|
|
int status;
|
|
|
|
shader = glCreateShader (type);
|
|
glShaderSource (shader, 1, &src, NULL);
|
|
glCompileShader (shader);
|
|
|
|
glGetShaderiv (shader, GL_COMPILE_STATUS, &status);
|
|
if (status == GL_FALSE)
|
|
{
|
|
int log_len;
|
|
char *buffer;
|
|
|
|
glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len);
|
|
|
|
buffer = g_malloc (log_len + 1);
|
|
glGetShaderInfoLog (shader, log_len, NULL, buffer);
|
|
|
|
g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_COMPILATION_FAILED,
|
|
"Compile failure in %s shader:\n%s",
|
|
type == GL_VERTEX_SHADER ? "vertex" : "fragment",
|
|
buffer);
|
|
|
|
g_free (buffer);
|
|
|
|
glDeleteShader (shader);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return shader;
|
|
}
|
|
|
|
static gboolean
|
|
init_shaders (GtkShadertoy *shadertoy,
|
|
const char *vertex_source,
|
|
const char *fragment_source,
|
|
GError **error)
|
|
{
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
GLuint vertex, fragment;
|
|
GLuint program = 0;
|
|
int status;
|
|
gboolean res = TRUE;
|
|
|
|
vertex = create_shader (GL_VERTEX_SHADER, vertex_source, error);
|
|
if (vertex == 0)
|
|
return FALSE;
|
|
|
|
fragment = create_shader (GL_FRAGMENT_SHADER, fragment_source, error);
|
|
if (fragment == 0)
|
|
{
|
|
glDeleteShader (vertex);
|
|
return FALSE;
|
|
}
|
|
|
|
program = glCreateProgram ();
|
|
glAttachShader (program, vertex);
|
|
glAttachShader (program, fragment);
|
|
|
|
glLinkProgram (program);
|
|
|
|
glGetProgramiv (program, GL_LINK_STATUS, &status);
|
|
if (status == GL_FALSE)
|
|
{
|
|
int log_len;
|
|
char *buffer;
|
|
|
|
glGetProgramiv (program, GL_INFO_LOG_LENGTH, &log_len);
|
|
|
|
buffer = g_malloc (log_len + 1);
|
|
glGetProgramInfoLog (program, log_len, NULL, buffer);
|
|
|
|
g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_LINK_FAILED,
|
|
"Linking failure:\n%s", buffer);
|
|
res = FALSE;
|
|
|
|
g_free (buffer);
|
|
|
|
glDeleteProgram (program);
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (priv->program != 0)
|
|
glDeleteProgram (priv->program);
|
|
|
|
priv->program = program;
|
|
priv->resolution_location = glGetUniformLocation (program, "iResolution");
|
|
priv->time_location = glGetUniformLocation (program, "iTime");
|
|
priv->timedelta_location = glGetUniformLocation (program, "iTimeDelta");
|
|
priv->frame_location = glGetUniformLocation (program, "iFrame");
|
|
priv->mouse_location = glGetUniformLocation (program, "iMouse");
|
|
|
|
glDetachShader (program, vertex);
|
|
glDetachShader (program, fragment);
|
|
|
|
out:
|
|
/* These are now owned by the program and can be deleted */
|
|
glDeleteShader (vertex);
|
|
glDeleteShader (fragment);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
gtk_shadertoy_realize_shader (GtkShadertoy *shadertoy)
|
|
{
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
char *fragment_shader;
|
|
GError *error = NULL;
|
|
|
|
fragment_shader = g_strconcat (fragment_prefix, priv->image_shader, fragment_suffix, NULL);
|
|
if (!init_shaders (shadertoy, shadertoy_vertex_shader, fragment_shader, &error))
|
|
{
|
|
priv->error_set = TRUE;
|
|
gtk_gl_area_set_error (GTK_GL_AREA (shadertoy), error);
|
|
g_error_free (error);
|
|
}
|
|
g_free (fragment_shader);
|
|
|
|
/* Start new shader at time zero */
|
|
priv->first_frame_time = 0;
|
|
priv->first_frame = 0;
|
|
|
|
priv->image_shader_dirty = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_shadertoy_render (GtkGLArea *area,
|
|
GdkGLContext *context)
|
|
{
|
|
GtkShadertoy *shadertoy = GTK_SHADERTOY (area);
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
|
|
if (gtk_gl_area_get_error (area) != NULL)
|
|
return FALSE;
|
|
|
|
if (priv->image_shader_dirty)
|
|
gtk_shadertoy_realize_shader (shadertoy);
|
|
|
|
/* Clear the viewport */
|
|
glClearColor (0.0, 0.0, 0.0, 1.0);
|
|
glClear (GL_COLOR_BUFFER_BIT);
|
|
|
|
glUseProgram (priv->program);
|
|
|
|
/* Update uniforms */
|
|
if (priv->resolution_location != -1)
|
|
glUniform3fv (priv->resolution_location, 1, priv->resolution);
|
|
if (priv->time_location != -1)
|
|
glUniform1f (priv->time_location, priv->time);
|
|
if (priv->timedelta_location != -1)
|
|
glUniform1f (priv->timedelta_location, priv->timedelta);
|
|
if (priv->frame_location != -1)
|
|
glUniform1i (priv->frame_location, priv->frame);
|
|
if (priv->mouse_location != -1)
|
|
glUniform4fv (priv->mouse_location, 1, priv->mouse);
|
|
|
|
/* Use the vertices in our buffer */
|
|
glBindBuffer (GL_ARRAY_BUFFER, priv->buffer);
|
|
glEnableVertexAttribArray (0);
|
|
glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 0, 0);
|
|
|
|
glDrawArrays (GL_TRIANGLES, 0, 6);
|
|
|
|
/* We finished using the buffers and program */
|
|
glDisableVertexAttribArray (0);
|
|
glBindBuffer (GL_ARRAY_BUFFER, 0);
|
|
glUseProgram (0);
|
|
|
|
/* Flush the contents of the pipeline */
|
|
glFlush ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
const char *
|
|
gtk_shadertoy_get_image_shader (GtkShadertoy *shadertoy)
|
|
{
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
|
|
return priv->image_shader;
|
|
}
|
|
|
|
void
|
|
gtk_shadertoy_set_image_shader (GtkShadertoy *shadertoy,
|
|
const char *shader)
|
|
{
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
|
|
g_free (priv->image_shader);
|
|
priv->image_shader = g_strdup (shader);
|
|
|
|
/* Don't override error we didn't set it ourselves */
|
|
if (priv->error_set)
|
|
{
|
|
gtk_gl_area_set_error (GTK_GL_AREA (shadertoy), NULL);
|
|
priv->error_set = FALSE;
|
|
}
|
|
priv->image_shader_dirty = TRUE;
|
|
}
|
|
|
|
static void
|
|
gtk_shadertoy_realize (GtkWidget *widget)
|
|
{
|
|
GtkGLArea *glarea = GTK_GL_AREA (widget);
|
|
GtkShadertoy *shadertoy = GTK_SHADERTOY (widget);
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
|
|
/* Draw two triangles across whole screen */
|
|
const GLfloat vertex_data[] = {
|
|
-1.0f, -1.0f, 0.f, 1.f,
|
|
-1.0f, 1.0f, 0.f, 1.f,
|
|
1.0f, 1.0f, 0.f, 1.f,
|
|
|
|
-1.0f, -1.0f, 0.f, 1.f,
|
|
1.0f, 1.0f, 0.f, 1.f,
|
|
1.0f, -1.0f, 0.f, 1.f,
|
|
};
|
|
|
|
GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->realize (widget);
|
|
|
|
gtk_gl_area_make_current (glarea);
|
|
if (gtk_gl_area_get_error (glarea) != NULL)
|
|
return;
|
|
|
|
glGenVertexArrays (1, &priv->vao);
|
|
glBindVertexArray (priv->vao);
|
|
|
|
glGenBuffers (1, &priv->buffer);
|
|
glBindBuffer (GL_ARRAY_BUFFER, priv->buffer);
|
|
glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW);
|
|
glBindBuffer (GL_ARRAY_BUFFER, 0);
|
|
|
|
gtk_shadertoy_realize_shader (shadertoy);
|
|
}
|
|
|
|
static void
|
|
gtk_shadertoy_unrealize (GtkWidget *widget)
|
|
{
|
|
GtkGLArea *glarea = GTK_GL_AREA (widget);
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private ((GtkShadertoy *) widget);
|
|
|
|
gtk_gl_area_make_current (glarea);
|
|
if (gtk_gl_area_get_error (glarea) == NULL)
|
|
{
|
|
if (priv->buffer != 0)
|
|
glDeleteBuffers (1, &priv->buffer);
|
|
|
|
if (priv->vao != 0)
|
|
glDeleteVertexArrays (1, &priv->vao);
|
|
|
|
if (priv->program != 0)
|
|
glDeleteProgram (priv->program);
|
|
}
|
|
|
|
GTK_WIDGET_CLASS (gtk_shadertoy_parent_class)->unrealize (widget);
|
|
}
|
|
|
|
static gboolean
|
|
gtk_shadertoy_tick (GtkWidget *widget,
|
|
GdkFrameClock *frame_clock,
|
|
gpointer user_data)
|
|
{
|
|
GtkShadertoy *shadertoy = GTK_SHADERTOY (widget);
|
|
GtkShadertoyPrivate *priv = gtk_shadertoy_get_instance_private (shadertoy);
|
|
gint64 frame_time;
|
|
gint64 frame;
|
|
float previous_time;
|
|
|
|
frame = gdk_frame_clock_get_frame_counter (frame_clock);
|
|
frame_time = gdk_frame_clock_get_frame_time (frame_clock);
|
|
|
|
if (priv->first_frame_time == 0)
|
|
{
|
|
priv->first_frame_time = frame_time;
|
|
priv->first_frame = frame;
|
|
previous_time = 0;
|
|
}
|
|
else
|
|
previous_time = priv->time;
|
|
|
|
priv->time = (frame_time - priv->first_frame_time) / 1000000.0f;
|
|
priv->frame = frame - priv->first_frame;
|
|
priv->timedelta = priv->time - previous_time;
|
|
|
|
gtk_widget_queue_draw (widget);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|