bb5bb2d3e21088367cddfdacd6aeaf15beb034bf

Author
outfoxxed <outfoxxed@outfoxxed.me>
Committer
outfoxxed <outfoxxed@outfoxxed.me>
Date

Message

add borders to hy3 tabs

Diff

  1diff --git a/CHANGELOG.md b/CHANGELOG.md
  2index 43a8dbb1e4a7dd0f8a0d2d08f6aa7ee038784400..f23978f187f0a42b48fd34d7ed2b4840376d682e 100644
  3--- a/CHANGELOG.md
  4+++ b/CHANGELOG.md
  5@@ -1,5 +1,11 @@
  6 # Changelog
  7 
  8+# Upcoming
  9+
 10+- Added configurable borders to tabs
 11+- Renamed `hy3:tabs:rounding` to `hy3:tabs:radius`.
 12+- Changed default tab style
 13+
 14 # hl0.46.0 and before
 15 
 16 - Only compatibility fixes.
 17diff --git a/CMakeLists.txt b/CMakeLists.txt
 18index 832d21fb7b0298e122daa95f8d386285975410f3..59297b5ca850b34b6b86628cc8292e5c6b10c3ad 100644
 19--- a/CMakeLists.txt
 20+++ b/CMakeLists.txt
 21@@ -20,8 +20,19 @@ add_library(hy3 SHARED
 22 	src/Hy3Node.cpp
 23 	src/TabGroup.cpp
 24 	src/SelectionHook.cpp
 25+	src/shaders.cpp
 26+	src/render.cpp
 27 )
 28 
 29+configure_file(src/simple_quad.vert ${CMAKE_CURRENT_BINARY_DIR}/src/simple_quad.vert COPYONLY)
 30+file(READ ${CMAKE_CURRENT_BINARY_DIR}/src/simple_quad.vert SHADER_SIMPLE_QUAD)
 31+
 32+configure_file(src/border_rect.frag ${CMAKE_CURRENT_BINARY_DIR}/src/border_rect.frag COPYONLY)
 33+file(READ ${CMAKE_CURRENT_BINARY_DIR}/src/border_rect.frag SHADER_BORDER_RECT)
 34+
 35+configure_file(src/shader_content.hpp.in src/shader_content.hpp @ONLY)
 36+target_include_directories(hy3 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src)
 37+
 38 option(HY3_NO_VERSION_CHECK "Disable hyprland version check" FALSE)
 39 
 40 if (HY3_NO_VERSION_CHECK)
 41diff --git a/README.md b/README.md
 42index ab2eaa13edaff425a787247013c1ac4f6ef8edcf..740215e64d5f8d37b04e08f4b8c99dd85c79f23d 100644
 43--- a/README.md
 44+++ b/README.md
 45@@ -240,22 +240,22 @@ plugin {
 46     # tab group settings
 47     tabs {
 48       # height of the tab bar
 49-      height = <int> # default: 15
 50+      height = <int> # default: 22
 51 
 52       # padding between the tab bar and its focused node
 53-      padding = <int> # default: 5
 54+      padding = <int> # default: 6
 55 
 56       # the tab bar should animate in/out from the top instead of below the window
 57       from_top = <bool> # default: false
 58 
 59-      # rounding of tab bar corners
 60-      rounding = <int> # default: 3
 61+      # radius of tab bar corners
 62+      radius = <int> # default: 6
 63 
 64       # render the window title on the bar
 65       render_text = <bool> # default: true
 66 
 67       # center the window title
 68-      text_center = <bool> # default: false
 69+      text_center = <bool> # default: true
 70 
 71       # font to render the window title with
 72       text_font = <string> # default: Sans
 73@@ -266,23 +266,20 @@ plugin {
 74       # left padding of the window title
 75       text_padding = <int> # default: 3
 76 
 77-      # active tab bar segment color
 78-      col.active = <color> # default: 0xff32b4ff
 79+      # active tab bar segment colors
 80+      col.active = <color> # default: rgba(50a0e0b0)
 81+	  col.border.active = <color> # default: rgba(70b0c0b0)
 82+      col.text.active = <color> # default: rgba(000000ff)
 83 
 84-      # urgent tab bar segment color
 85-      col.urgent = <color> # default: 0xffff4f4f
 86+      # urgent tab bar segment colors
 87+      col.urgent = <color> # default: rgba(ff4f4fb0)
 88+      col.border.urgent = <color> # default: rgba(ff8080b0)
 89+      col.text.urgent = <color> # default: rgba(000000ff)
 90 
 91-      # inactive tab bar segment color
 92-      col.inactive = <color> # default: 0x80808080
 93-
 94-      # active tab bar text color
 95-      col.text.active = <color> # default: 0xff000000
 96-
 97-      # urgent tab bar text color
 98-      col.text.urgent = <color> # default: 0xff000000
 99-
100-      # inactive tab bar text color
101-      col.text.inactive = <color> # default: 0xff000000
102+      # inactive tab bar segment colors
103+      col.inactive = <color> # default: rgba(30303090)
104+	  col.border.inactive = <color> # default: rgba(505050b0)
105+      col.text.inactive = <color> # default: rgba(ffffffff)
106     }
107 
108     # autotiling settings
109diff --git a/src/TabGroup.cpp b/src/TabGroup.cpp
110index 3071826e753778874c71048c7d3352d7708c1367..01b0695e3412e75557a73933fb8a4448c3249283 100644
111--- a/src/TabGroup.cpp
112+++ b/src/TabGroup.cpp
113@@ -1,5 +1,6 @@
114 #include "TabGroup.hpp"
115 
116+#include <GLES2/gl2.h>
117 #include <cairo/cairo.h>
118 #include <hyprland/src/Compositor.hpp>
119 #include <hyprland/src/desktop/DesktopTypes.hpp>
120@@ -15,6 +16,7 @@
121 
122 #include "globals.hpp"
123 #include "log.hpp"
124+#include "render.hpp"
125 
126 // This is a workaround CHyprColor not having working arithmetic operator...
127 static inline CHyprColor
128@@ -120,18 +122,24 @@ bool Hy3TabBarEntry::shouldRemove() {
129 void Hy3TabBarEntry::render(float scale, CBox& box, float opacity_mul) {
130 	auto opacity = opacity_mul * this->fade_opacity.value();
131 
132-	static const auto s_rounding = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:rounding");
133+	// clang-format off
134+	static const auto s_radius = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:radius");
135+	static const auto border_width = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:border_width");
136 	static const auto col_active = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.active");
137+	static const auto col_border_active = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.border.active");
138 	static const auto col_urgent = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.urgent");
139+	static const auto col_border_urgent = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.border.urgent");
140 	static const auto col_inactive = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.inactive");
141+	static const auto col_border_inactive = ConfigValue<Hyprlang::INT>("plugin:hy3:tabs:col.border.inactive");
142+	// clang-format on
143 
144-	auto rounding =
145-	    std::min((double) *s_rounding * scale, std::min(box.width * 0.5, box.height * 0.5));
146+	auto radius = std::min((double) *s_radius * scale, std::min(box.width * 0.5, box.height * 0.5));
147 
148 	auto focused = this->focused.value();
149 	auto urgent = this->urgent.value();
150 	auto inactive = 1.0f - (focused + urgent);
151-	CHyprColor c = merge_colors(
152+
153+	auto color = merge_colors(
154 	    focused,
155 	    CHyprColor(*col_active),
156 	    urgent,
157@@ -140,9 +148,24 @@ void Hy3TabBarEntry::render(float scale, CBox& box, float opacity_mul) {
158 	    CHyprColor(*col_inactive)
159 	);
160 
161-	c.a *= opacity;
162+	auto border_color = merge_colors(
163+	    focused,
164+	    CHyprColor(*col_border_active),
165+	    urgent,
166+	    CHyprColor(*col_border_urgent),
167+	    inactive,
168+	    CHyprColor(*col_border_inactive)
169+	);
170+
171+	color.a *= opacity;
172+	border_color.a *= opacity;
173+
174+	box.round();
175+
176+	// sometimes enabled before our renderer is called
177+	glDisable(GL_SCISSOR_TEST);
178 
179-	g_pHyprOpenGL->renderRect(&box, c, rounding);
180+	Hy3Render::renderBorderRect(box, color, border_color, *border_width, radius);
181 	this->renderText(scale, box, opacity);
182 }
183 
184diff --git a/src/border_rect.frag b/src/border_rect.frag
185new file mode 100644
186index 0000000000000000000000000000000000000000..e2d6684aa8721bcb98f2a6e5f07bc11dfda2c468
187--- /dev/null
188+++ b/src/border_rect.frag
189@@ -0,0 +1,53 @@
190+precision highp float;
191+varying highp vec4 v_color;
192+varying highp vec2 v_texcoord;
193+
194+uniform highp vec4 fillColor;
195+uniform highp vec4 borderColor;
196+uniform highp vec2 pixelSize;
197+uniform float outerRadius;
198+uniform float borderWidth;
199+
200+void main() {
201+	highp vec2 pixCoord = v_texcoord * pixelSize;
202+	highp vec2 cornerDist = min(pixCoord, pixelSize - pixCoord);
203+
204+	gl_FragColor = fillColor;
205+
206+	if (cornerDist.x <= outerRadius && cornerDist.y <= outerRadius) {
207+		highp vec2 vcornerDist = vec2(outerRadius) - cornerDist;
208+		float distSq = vcornerDist.x * vcornerDist.x + vcornerDist.y * vcornerDist.y;
209+
210+		// See https://github.com/hyprwm/Hyprland/blob/e75e2cdac79417ffdbbbe903f72668953483a4e7/src/render/shaders/SharedValues.hpp#L3
211+		const float SMOOTHING_CONSTANT = 0.58758063398831095317;
212+		const float SMOOTHING_CONSTANT_2X = SMOOTHING_CONSTANT * 2.0;
213+
214+		float innerRadius = outerRadius - borderWidth;
215+		float outerTest1 = outerRadius + SMOOTHING_CONSTANT_2X;
216+		float outerTest2 = outerRadius - SMOOTHING_CONSTANT_2X;
217+		float innerTest1 = innerRadius + SMOOTHING_CONSTANT_2X;
218+		float innerTest2 = innerRadius - SMOOTHING_CONSTANT_2X;
219+
220+		float dist;
221+		bool calculatedDist = false;
222+
223+		if (distSq > outerTest1 * outerTest1) {
224+			discard;
225+		} else if (distSq > outerTest2 * outerTest2) {
226+			dist = sqrt(distSq);
227+			calculatedDist = true;
228+			float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - outerRadius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT_2X));
229+			gl_FragColor = borderColor * normalized;
230+		} else if (distSq > innerTest2 * innerTest2) {
231+			gl_FragColor = borderColor;
232+		}
233+
234+		if (distSq > innerTest2 * innerTest2 && distSq <= innerTest1 * innerTest1) {
235+			if (!calculatedDist) dist = sqrt(distSq);
236+			float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - innerRadius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT_2X));
237+			gl_FragColor = gl_FragColor * (1.0 - normalized) + fillColor * normalized;
238+		}
239+	} else if (cornerDist.x <= borderWidth || cornerDist.y <= borderWidth) {
240+		gl_FragColor = borderColor;
241+	}
242+}
243diff --git a/src/main.cpp b/src/main.cpp
244index 96a36cb582cf4f64491db68d060cec01cb5ceede..87c39b0b54d1058b63c55b7b2d20740b4048f009 100644
245--- a/src/main.cpp
246+++ b/src/main.cpp
247@@ -1,4 +1,5 @@
248 #include <hyprland/src/Compositor.hpp>
249+#include <hyprland/src/config/ConfigDataValues.hpp>
250 #include <hyprland/src/plugins/PluginAPI.hpp>
251 #include <hyprland/src/version.h>
252 
253@@ -27,11 +28,10 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
254 	selection_hook::init();
255 
256 #define CONF(NAME, TYPE, VALUE)                                                                    \
257-	HyprlandAPI::addConfigValue(                                                                     \
258-	    PHANDLE,                                                                                     \
259-	    "plugin:hy3:" NAME,                                                                          \
260-	    Hyprlang::CConfigValue((Hyprlang::TYPE) VALUE)                                               \
261-	)
262+	HyprlandAPI::addConfigValue(PHANDLE, "plugin:hy3:" NAME, Hyprlang::CConfigValue((TYPE) VALUE))
263+
264+	using Hyprlang::INT;
265+	using Hyprlang::STRING;
266 
267 	// general
268 	CONF("no_gaps_when_only", INT, 0);
269@@ -40,21 +40,25 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
270 	CONF("tab_first_window", INT, 0);
271 
272 	// tabs
273-	CONF("tabs:height", INT, 15);
274+	CONF("tabs:height", INT, 22);
275 	CONF("tabs:padding", INT, 5);
276 	CONF("tabs:from_top", INT, 0);
277-	CONF("tabs:rounding", INT, 3);
278+	CONF("tabs:radius", INT, 6);
279+	CONF("tabs:border_width", INT, 2);
280 	CONF("tabs:render_text", INT, 1);
281-	CONF("tabs:text_center", INT, 0);
282+	CONF("tabs:text_center", INT, 1);
283 	CONF("tabs:text_font", STRING, "Sans");
284 	CONF("tabs:text_height", INT, 8);
285 	CONF("tabs:text_padding", INT, 3);
286-	CONF("tabs:col.active", INT, 0xff32b4ff);
287-	CONF("tabs:col.urgent", INT, 0xffff4f4f);
288-	CONF("tabs:col.inactive", INT, 0x80808080);
289+	CONF("tabs:col.active", INT, 0xb050a0e0);
290+	CONF("tabs:col.border.active", INT, 0xb070b0c0);
291+	CONF("tabs:col.urgent", INT, 0xb0ff4f4f);
292+	CONF("tabs:col.border.urgent", INT, 0xb0ff8080);
293+	CONF("tabs:col.inactive", INT, 0x90303030);
294+	CONF("tabs:col.border.inactive", INT, 0xb0505050);
295 	CONF("tabs:col.text.active", INT, 0xff000000);
296 	CONF("tabs:col.text.urgent", INT, 0xff000000);
297-	CONF("tabs:col.text.inactive", INT, 0xff000000);
298+	CONF("tabs:col.text.inactive", INT, 0xffffffff);
299 
300 	// autotiling
301 	CONF("autotile:enable", INT, 0);
302diff --git a/src/render.cpp b/src/render.cpp
303new file mode 100644
304index 0000000000000000000000000000000000000000..db30cea440f777252aaec9e330fe6488ce46e7d3
305--- /dev/null
306+++ b/src/render.cpp
307@@ -0,0 +1,68 @@
308+#include "render.hpp"
309+
310+#include <GLES2/gl2.h>
311+#include <hyprland/src/helpers/math/Math.hpp>
312+#include <hyprland/src/render/OpenGL.hpp>
313+
314+#include "shaders.hpp"
315+
316+void Hy3Render::renderBorderRect(
317+    const CBox& box,
318+    const CHyprColor& fillColor,
319+    const CHyprColor& borderColor,
320+    int borderWidth,
321+    int radius
322+) {
323+	static auto& shader = Hy3Shaders::instance()->border_rect;
324+	auto& rdata = g_pHyprOpenGL->m_RenderData;
325+
326+	auto rbox = box;
327+	rdata.renderModif.applyToBox(rbox);
328+
329+	auto matrix = rdata.monitorProjection.projectBox(
330+	    rbox,
331+	    wlTransformToHyprutils(invertTransform(rdata.pMonitor->transform)),
332+	    rbox.rot
333+	);
334+
335+	auto glMatrix = rdata.projection.copy().multiply(matrix);
336+
337+	glUseProgram(shader.program);
338+
339+#ifndef GLES2
340+	glUniformMatrix3fv(shader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
341+#else
342+	glMatrix.transpose();
343+	glUniformMatrix3fv(shader.proj, 1, GL_FALSE, glMatrix.getMatrix().data());
344+#endif
345+
346+	// premultiplied
347+	glUniform4f(
348+	    shader.fillColor,
349+	    fillColor.r * fillColor.a,
350+	    fillColor.g * fillColor.a,
351+	    fillColor.b * fillColor.a,
352+	    fillColor.a
353+	);
354+
355+	if (borderWidth != 0) {
356+		glUniform4f(
357+		    shader.borderColor,
358+		    borderColor.r * borderColor.a,
359+		    borderColor.g * borderColor.a,
360+		    borderColor.b * borderColor.a,
361+		    borderColor.a
362+		);
363+	}
364+
365+	glUniform2f(shader.pixelSize, rbox.w, rbox.h);
366+	glUniform1f(shader.outerRadius, radius);
367+	glUniform1f(shader.borderWidth, borderWidth);
368+
369+	glVertexAttribPointer(shader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
370+	glEnableVertexAttribArray(shader.posAttrib);
371+
372+	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
373+
374+	glDisableVertexAttribArray(shader.posAttrib);
375+}
376diff --git a/src/render.hpp b/src/render.hpp
377new file mode 100644
378index 0000000000000000000000000000000000000000..6efdf1d1e27dc60163d88c4be424d1f5b0744236
379--- /dev/null
380+++ b/src/render.hpp
381@@ -0,0 +1,14 @@
382+#pragma once
383+#include <hyprland/src/helpers/Color.hpp>
384+#include <hyprutils/math/Box.hpp>
385+
386+class Hy3Render {
387+public:
388+	static void renderBorderRect(
389+	    const CBox& box,
390+	    const CHyprColor& fillColor,
391+	    const CHyprColor& borderColor,
392+	    int borderWidth,
393+	    int radius
394+	);
395+};
396diff --git a/src/shader_content.hpp.in b/src/shader_content.hpp.in
397new file mode 100644
398index 0000000000000000000000000000000000000000..6e242a452fe1910df06988b1c4ba869ae2418239
399--- /dev/null
400+++ b/src/shader_content.hpp.in
401@@ -0,0 +1,4 @@
402+#include <string_view>
403+
404+constexpr std::string_view SHADER_SIMPLE_QUAD = R"(@SHADER_SIMPLE_QUAD@)";
405+constexpr std::string_view SHADER_BORDER_RECT_FRAG = R"(@SHADER_BORDER_RECT@)";
406diff --git a/src/shaders.cpp b/src/shaders.cpp
407new file mode 100644
408index 0000000000000000000000000000000000000000..88f02f407f817da6cb0608f0dcdfb676e533c059
409--- /dev/null
410+++ b/src/shaders.cpp
411@@ -0,0 +1,90 @@
412+#include "shaders.hpp"
413+#include <csignal>
414+
415+#include <GLES2/gl2.h>
416+#include <hyprland/src/render/OpenGL.hpp>
417+
418+#include "shader_content.hpp"
419+
420+Hy3Shaders::Hy3Shaders() {
421+	{
422+		auto& s = this->border_rect;
423+		s.program = Hy3Shaders::createProgram(SHADER_SIMPLE_QUAD, SHADER_BORDER_RECT_FRAG);
424+		s.posAttrib = glGetAttribLocation(s.program, "pos");
425+		s.proj = glGetUniformLocation(s.program, "proj");
426+		s.pixelSize = glGetUniformLocation(s.program, "pixelSize");
427+		s.fillColor = glGetUniformLocation(s.program, "fillColor");
428+		s.borderColor = glGetUniformLocation(s.program, "borderColor");
429+		s.outerRadius = glGetUniformLocation(s.program, "outerRadius");
430+		s.borderWidth = glGetUniformLocation(s.program, "borderWidth");
431+	}
432+}
433+
434+Hy3Shaders::~Hy3Shaders() { glDeleteProgram(this->border_rect.program); }
435+
436+Hy3Shaders* Hy3Shaders::instance() {
437+	static auto* INSTANCE = new Hy3Shaders();
438+	return INSTANCE;
439+}
440+
441+GLuint Hy3Shaders::createProgram(const std::string_view vert, const std::string_view frag) {
442+	auto vshader = compileShader(GL_VERTEX_SHADER, vert);
443+	auto fshader = compileShader(GL_FRAGMENT_SHADER, frag);
444+
445+	auto program = glCreateProgram();
446+	glAttachShader(program, vshader);
447+	glAttachShader(program, fshader);
448+	glLinkProgram(program);
449+
450+	glDetachShader(program, vshader);
451+	glDetachShader(program, fshader);
452+	glDeleteShader(vshader);
453+	glDeleteShader(fshader);
454+
455+	GLint ok;
456+	glGetProgramiv(program, GL_LINK_STATUS, &ok);
457+
458+	if (ok != GL_TRUE) {
459+		Hy3Shaders::logShaderError(program, true);
460+	}
461+
462+	return program;
463+}
464+
465+GLuint Hy3Shaders::compileShader(GLuint type, const std::string_view src) {
466+	auto srcdata = src.data();
467+	GLint srclen = src.length();
468+
469+	auto shader = glCreateShader(type);
470+	glShaderSource(shader, 1, &srcdata, &srclen);
471+	glCompileShader(shader);
472+
473+	GLint ok;
474+	glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
475+
476+	if (ok != GL_TRUE) {
477+		Hy3Shaders::logShaderError(shader, false);
478+	}
479+
480+	return shader;
481+}
482+
483+void Hy3Shaders::logShaderError(GLuint shader, bool program) {
484+	GLint logBufferLength = 0;
485+
486+	if (program) glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &logBufferLength);
487+	else glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logBufferLength);
488+
489+	auto* logBuffer = new char[logBufferLength];
490+
491+	if (program) glGetProgramInfoLog(shader, logBufferLength, &logBufferLength, logBuffer);
492+	else glGetShaderInfoLog(shader, logBufferLength, &logBufferLength, logBuffer);
493+
494+	if (program) {
495+		Debug::log(ERR, "Failed to link shader program: {}", logBuffer);
496+	} else {
497+		Debug::log(ERR, "Failed to compile shader: {}", logBuffer);
498+	}
499+
500+	raise(SIGABRT);
501+}
502diff --git a/src/shaders.hpp b/src/shaders.hpp
503new file mode 100644
504index 0000000000000000000000000000000000000000..1fb39a003909a8f1e97bdd8f46a5d64f5db980ca
505--- /dev/null
506+++ b/src/shaders.hpp
507@@ -0,0 +1,30 @@
508+#pragma once
509+
510+#include <string_view>
511+
512+#include <GLES2/gl2.h>
513+#include <hyprland/src/render/OpenGL.hpp>
514+
515+class Hy3Shaders {
516+public:
517+	struct {
518+		GLuint program;
519+		GLuint posAttrib;
520+		GLint proj;
521+		GLint fillColor;
522+		GLint borderColor;
523+		GLint pixelSize;
524+		GLint outerRadius;
525+		GLint borderWidth;
526+	} border_rect;
527+
528+	static Hy3Shaders* instance();
529+
530+private:
531+	Hy3Shaders();
532+	~Hy3Shaders();
533+
534+	static GLuint createProgram(const std::string_view vert, const std::string_view frag);
535+	static GLuint compileShader(GLuint type, const std::string_view src);
536+	static void logShaderError(GLuint shader, bool program);
537+};
538diff --git a/src/simple_quad.vert b/src/simple_quad.vert
539new file mode 100644
540index 0000000000000000000000000000000000000000..0398200e810f607690ba2a0880851c7f8377da64
541--- /dev/null
542+++ b/src/simple_quad.vert
543@@ -0,0 +1,11 @@
544+attribute highp vec2 pos;
545+attribute highp vec2 texcoord;
546+
547+uniform mat3 proj;
548+
549+varying highp vec2 v_texcoord;
550+
551+void main() {
552+	gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
553+	v_texcoord = pos;
554+}