<commit_before>/* Copyright (c) 2013-2019 <NAME>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/gba/renderers/gl.h>

#if defined(BUILD_GLES2) || defined(BUILD_GLES3)

#include <mgba/core/cache-set.h>
#include <mgba/internal/arm/macros.h>
#include <mgba/internal/gba/io.h>
#include <mgba/internal/gba/renderers/cache-set.h>
#include <mgba-util/memory.h>

static void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer);
static void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer);
static void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer);
static void GBAVideoGLRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
static void GBAVideoGLRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
static void GBAVideoGLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
static void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer);
static void GBAVideoGLRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels);
static void GBAVideoGLRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);

static void GBAVideoGLRendererUpdateDISPCNT(struct GBAVideoGLRenderer* renderer);
static void GBAVideoGLRendererWriteBGCNT(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBGX_LO(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBGX_HI(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBGY_LO(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBGY_HI(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBLDCNT(struct GBAVideoGLRenderer* renderer, uint16_t value);

static void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GBAObj* sprite, int y, int spriteY);
static void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawBackgroundMode3(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y);

static void _cleanRegister(struct GBAVideoGLRenderer* renderer, int address, uint16_t value);
static void _drawScanlines(struct GBAVideoGLRenderer* renderer, int lastY);
static void _finalizeLayers(struct GBAVideoGLRenderer* renderer);

#define TEST_LAYER_ENABLED(X) !glRenderer->d.disableBG[X] && glRenderer->bg[X].enabled == 4

struct GBAVideoGLUniform {
	const char* name;
	int type;
};

#define PALETTE_ENTRY "#define PALETTE_ENTRY(x) (vec3((ivec3(0x1F, 0x3E0, 0x7C00) & (x)) >> ivec3(0, 5, 10)) / 31.)\n"

static const GLchar* const _gles3Header =
	"#version 300 es\n"
	"#define OUT(n) layout(location = n)\n"
	PALETTE_ENTRY
	"precision highp float;\n"
	"precision highp int;\n"
	"precision highp sampler2D;\n"
	"precision highp isampler2D;\n";

static const GLchar* const _gl3Header =
	"#version 130\n"
	"#define OUT(n)\n"
	PALETTE_ENTRY
	"precision highp float;\n";

static const char* const _vertexShader =
	"in vec2 position;\n"
	"uniform ivec2 loc;\n"
	"uniform ivec2 maxPos;\n"
	"out vec2 texCoord;\n"

	"void main() {\n"
	"	vec2 local = vec2(position.x, float(position.y * float(loc.x) + float(loc.y)) / float(maxPos.y));\n"
	"	gl_Position = vec4((local * 2. - 1.) * vec2(sign(maxPos)), 0., 1.);\n"
	"	texCoord = local * vec2(abs(maxPos));\n"
	"}";

static const char* const _renderTile16 =
	"vec4 renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
	"	int address = charBase + tile * 16 + (localCoord.x >> 2) + (localCoord.y << 1);\n"
	"	vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
	"	int entry = int(halfrow[3 - (localCoord.x & 3)] * 15.9);\n"
	"	if (entry == 0) {\n"
	"		discard;\n"
	"	}\n"
	"	int paletteEntry = palette[paletteId * 16 + entry];\n"
	"	vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
	"	return color;\n"
	"}";

static const char* const _renderTile256 =
	"vec4 renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
	"	int address = charBase + tile * 32 + (localCoord.x >> 1) + (localCoord.y << 2);\n"
	"	vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
	"	int entry = int(halfrow[3 - 2 * (localCoord.x & 1)] * 15.9);\n"
	"	int pal2 = int(halfrow[2 - 2 * (localCoord.x & 1)] * 15.9);\n"
	"	if ((pal2 | entry) == 0) {\n"
	"		discard;\n"
	"	}\n"
	"	int paletteEntry = palette[pal2 * 16 + entry];\n"
	"	vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
	"	return color;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsMode0[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_BG_VRAM, },
	{ "palette", GBA_GL_BG_PALETTE, },
	{ "screenBase", GBA_GL_BG_SCREENBASE, },
	{ "charBase", GBA_GL_BG_CHARBASE, },
	{ "size", GBA_GL_BG_SIZE, },
	{ "offset", GBA_GL_BG_OFFSET, },
	{ "inflags", GBA_GL_BG_INFLAGS, },
	{ "mosaic", GBA_GL_BG_MOSAIC, },
	{ 0 }
};

static const char* const _renderMode0 =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int palette[256];\n"
	"uniform int screenBase;\n"
	"uniform int charBase;\n"
	"uniform int size;\n"
	"uniform int offset[160];\n"
	"uniform ivec4 inflags;\n"
	"uniform ivec2 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"

	"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"

	"void main() {\n"
	"	ivec2 coord = ivec2(texCoord);\n"
	"	if (mosaic.x > 1) {\n"
	"		coord.x -= coord.x % mosaic.x;\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		coord.y -= coord.y % mosaic.y;\n"
	"	}\n"
	"	coord += (ivec2(0x3FF, 0x3FF000) & offset[int(texCoord.y)]) >> ivec2(0, 12);\n"
	"	if (size == 3) {\n"
	"		coord.y += (coord.y & 256) << 1;\n"
	"	}\n"
	"	if (size != 2) {\n"
	"		coord.y &= ~256;\n"
	"	}\n"
	"	if ((size & 1) == 1) {\n"
	"		coord.y += coord.x & 256;\n"
	"	}\n"
	"	coord &= ivec2(255, 511);\n"
	"	int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n"
	"	vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
	"	int tileFlags = int(map.g * 15.9);\n"
	"	if ((tileFlags & 4) == 4) {\n"
	"		coord.x ^= 7;\n"
	"	}\n"
	"	if ((tileFlags & 8) == 8) {\n"
	"		coord.y ^= 7;\n"
	"	}\n"
	"	int tile = int(map.a * 15.9) + int(map.b * 15.9) * 16 + (tileFlags & 0x3) * 256;\n"
	"	color = renderTile(tile, int(map.r * 15.9), coord & 7);\n"
	"	flags = inflags;\n"
	"}";

static const char* const _fetchTileOverflow =
	"vec4 fetchTile(ivec2 coord) {\n"
	"	int sizeAdjusted = (0x8000 << size) - 1;\n"
	"	coord &= sizeAdjusted;\n"
	"	return renderTile(coord);\n"
	"}";

static const char* const _fetchTileNoOverflow =
	"vec4 fetchTile(ivec2 coord) {\n"
	"	int sizeAdjusted = (0x8000 << size) - 1;\n"
	"	ivec2 outerCoord = coord & ~sizeAdjusted;\n"
	"	if ((outerCoord.x | outerCoord.y) != 0) {\n"
	"		discard;\n"
	"	}\n"
	"	return renderTile(coord);\n"
	"}";

static const struct GBAVideoGLUniform _uniformsMode2[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_BG_VRAM, },
	{ "palette", GBA_GL_BG_PALETTE, },
	{ "screenBase", GBA_GL_BG_SCREENBASE, },
	{ "charBase", GBA_GL_BG_CHARBASE, },
	{ "size", GBA_GL_BG_SIZE, },
	{ "inflags", GBA_GL_BG_INFLAGS, },
	{ "offset", GBA_GL_BG_OFFSET, },
	{ "transform", GBA_GL_BG_TRANSFORM, },
	{ "range", GBA_GL_BG_RANGE, },
	{ "mosaic", GBA_GL_BG_MOSAIC, },
	{ 0 }
};

static const char* const _interpolate =
	"vec2 interpolate(ivec2 arr[4], float x) {\n"
	"	float x1m = 1. - x;\n"
	"	return x1m * x1m * x1m * vec2(arr[0]) +"
		" 3. * x1m * x1m * x   * vec2(arr[1]) +"
		" 3. * x1m * x   * x   * vec2(arr[2]) +"
		"      x   * x   * x   * vec2(arr[3]);\n"
	"}\n"

	"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]) {\n"
	"	int start = max(range.x, y - 3);\n"
	"	mat[0] = transform[start + 0].xy;\n"
	"	aff[0] = transform[start + 0].zw;\n"
	"	mat[1] = transform[start + 1].xy;\n"
	"	aff[1] = transform[start + 1].zw;\n"
	"	mat[2] = transform[start + 2].xy;\n"
	"	aff[2] = transform[start + 2].zw;\n"
	"	mat[3] = transform[start + 3].xy;\n"
	"	aff[3] = transform[start + 3].zw;\n"
	"}\n";

static const char* const _renderMode2 =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int palette[256];\n"
	"uniform int screenBase;\n"
	"uniform int charBase;\n"
	"uniform int size;\n"
	"uniform ivec4 inflags;\n"
	"uniform ivec4 transform[160];\n"
	"uniform ivec2 range;\n"
	"uniform ivec2 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"

	"vec4 fetchTile(ivec2 coord);\n"
	"vec2 interpolate(ivec2 arr[4], float x);\n"
	"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"

	"vec4 renderTile(ivec2 coord) {\n"
	"	int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n"
	"	int mapAddress = screenBase + (map >> 1);\n"
	"	vec4 twomaps = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
	"	int tile = int(twomaps[3 - 2 * (map & 1)] * 15.9) + int(twomaps[2 - 2 * (map & 1)] * 15.9) * 16;\n"
	"	int address = charBase + tile * 32 + ((coord.x >> 9) & 3) + ((coord.y >> 6) & 0x1C);\n"
	"	vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
	"	int entry = int(halfrow[3 - ((coord.x >> 7) & 2)] * 15.9);\n"
	"	int pal2 = int(halfrow[2 - ((coord.x >> 7) & 2)] * 15.9);\n"
	"	if ((pal2 | entry) == 0) {\n"
	"		discard;\n"
	"	}\n"
	"	int paletteEntry = palette[pal2 * 16 + entry];\n"
	"	vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
	"	return color;\n"
	"}\n"

	"void main() {\n"
	"	ivec2 mat[4];\n"
	"	ivec2 offset[4];\n"
	"	vec2 incoord = texCoord;\n"
	"	if (mosaic.x > 1) {\n"
	"		incoord.x = float(int(incoord.x) % mosaic.x);\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		incoord.y = float(int(incoord.y) % mosaic.y);\n"
	"	}\n"
	"	loadAffine(int(incoord.y), mat, offset);\n"
	"	float y = fract(incoord.y);\n"
	"	float start = 0.75;\n"
	"	if (int(incoord.y) - range.x < 4) {\n"
	"		y = incoord.y - float(range.x);\n"
	"		start = 0.;\n"
	"	}\n"
	"	float lin = start + y * 0.25;\n"
	"	vec2 mixedTransform = interpolate(mat, lin);\n"
	"	vec2 mixedOffset = interpolate(offset, lin);\n"
	"	color = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n"
	"	flags = inflags;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsMode35[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_BG_VRAM, },
	{ "charBase", GBA_GL_BG_CHARBASE, },
	{ "size", GBA_GL_BG_SIZE, },
	{ "inflags", GBA_GL_BG_INFLAGS, },
	{ "offset", GBA_GL_BG_OFFSET, },
	{ "transform", GBA_GL_BG_TRANSFORM, },
	{ "range", GBA_GL_BG_RANGE, },
	{ "mosaic", GBA_GL_BG_MOSAIC, },
	{ 0 }
};

static const char* const _renderMode35 =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int charBase;\n"
	"uniform ivec2 size;\n"
	"uniform ivec4 inflags;\n"
	"uniform ivec4 transform[160];\n"
	"uniform ivec2 range;\n"
	"uniform ivec2 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"

	"vec2 interpolate(ivec2 arr[4], float x);\n"
	"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"

	"void main() {\n"
	"	ivec2 mat[4];\n"
	"	ivec2 offset[4];\n"
	"	vec2 incoord = texCoord;\n"
	"	if (mosaic.x > 1) {\n"
	"		incoord.x = float(int(incoord.x) % mosaic.x);\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		incoord.y = float(int(incoord.y) % mosaic.y);\n"
	"	}\n"
	"	loadAffine(int(incoord.y), mat, offset);\n"
	"	float y = fract(incoord.y);\n"
	"	float start = 0.75;\n"
	"	if (int(incoord.y) - range.x < 4) {\n"
	"		y = incoord.y - float(range.x);\n"
	"		start = 0.;\n"
	"	}\n"
	"	float lin = start + y * 0.25;\n"
	"	vec2 mixedTransform = interpolate(mat, lin);\n"
	"	vec2 mixedOffset = interpolate(offset, lin);\n"
	"	ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n"
	"	if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
	"		discard;\n"
	"	}\n"
	"	if (coord.y < 0 || coord.y >= (size.y << 8)) {\n"
	"		discard;\n"
	"	}\n"
	"	int address = charBase + (coord.x >> 8) + (coord.y >> 8) * size.x;\n"
	"	ivec4 entry = ivec4(texelFetch(vram, ivec2(address & 255, address >> 8), 0) * 15.9);\n"
	"	int sixteen = (entry.x << 12) | (entry.y << 8) | (entry.z << 4) | entry.w;\n"
	"	color = vec4(float(sixteen & 0x1F) / 31., float((sixteen >> 5) & 0x1F) / 31., float((sixteen >> 10) & 0x1F) / 31., 1.);\n"
	"	flags = inflags;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsMode4[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_BG_VRAM, },
	{ "palette", GBA_GL_BG_PALETTE, },
	{ "charBase", GBA_GL_BG_CHARBASE, },
	{ "size", GBA_GL_BG_SIZE, },
	{ "inflags", GBA_GL_BG_INFLAGS, },
	{ "offset", GBA_GL_BG_OFFSET, },
	{ "transform", GBA_GL_BG_TRANSFORM, },
	{ "range", GBA_GL_BG_RANGE, },
	{ "mosaic", GBA_GL_BG_MOSAIC, },
	{ 0 }
};

static const char* const _renderMode4 =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int palette[256];\n"
	"uniform int charBase;\n"
	"uniform ivec2 size;\n"
	"uniform ivec4 inflags;\n"
	"uniform ivec4 transform[160];\n"
	"uniform ivec2 range;\n"
	"uniform ivec2 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"

	"vec2 interpolate(ivec2 arr[4], float x);\n"
	"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"

	"void main() {\n"
	"	ivec2 mat[4];\n"
	"	ivec2 offset[4];\n"
	"	vec2 incoord = texCoord;\n"
	"	if (mosaic.x > 1) {\n"
	"		incoord.x = float(int(incoord.x) % mosaic.x);\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		incoord.y = float(int(incoord.y) % mosaic.y);\n"
	"	}\n"
	"	loadAffine(int(incoord.y), mat, offset);\n"
	"	float y = fract(incoord.y);\n"
	"	float start = 0.75;\n"
	"	if (int(incoord.y) - range.x < 4) {\n"
	"		y = incoord.y - float(range.x);\n"
	"		start = 0.;\n"
	"	}\n"
	"	float lin = start + y * 0.25;\n"
	"	vec2 mixedTransform = interpolate(mat, lin);\n"
	"	vec2 mixedOffset = interpolate(offset, lin);\n"
	"	ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n"
	"	if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
	"		discard;\n"
	"	}\n"
	"	if (coord.y < 0 || coord.y >= (size.y << 8)) {\n"
	"		discard;\n"
	"	}\n"
	"	int address = charBase + (coord.x >> 8) + (coord.y >> 8) * size.x;\n"
	"	vec4 twoEntries = texelFetch(vram, ivec2((address >> 1) & 255, address >> 9), 0);\n"
	"	ivec2 entry = ivec2(twoEntries[3 - 2 * (address & 1)] * 15.9, twoEntries[2 - 2 * (address & 1)] * 15.9);\n"
	"	int paletteEntry = palette[entry.y * 16 + entry.x];\n"
	"	color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
	"	flags = inflags;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsObj[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_OBJ_VRAM, },
	{ "palette", GBA_GL_OBJ_PALETTE, },
	{ "charBase", GBA_GL_OBJ_CHARBASE, },
	{ "stride", GBA_GL_OBJ_STRIDE, },
	{ "localPalette", GBA_GL_OBJ_LOCALPALETTE, },
	{ "inflags", GBA_GL_OBJ_INFLAGS, },
	{ "transform", GBA_GL_OBJ_TRANSFORM, },
	{ "dims", GBA_GL_OBJ_DIMS, },
	{ "objwin", GBA_GL_OBJ_OBJWIN, },
	{ "mosaic", GBA_GL_OBJ_MOSAIC, },
	{ 0 }
};

static const char* const _renderObj =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int palette[256];\n"
	"uniform int charBase;\n"
	"uniform int stride;\n"
	"uniform int localPalette;\n"
	"uniform ivec4 inflags;\n"
	"uniform mat2x2 transform;\n"
	"uniform ivec4 dims;\n"
	"uniform ivec4 objwin;\n"
	"uniform ivec4 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"
	"OUT(2) out ivec3 window;\n"

	"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"

	"void main() {\n"
	"	vec2 incoord = texCoord;\n"
	"	if (mosaic.x > 1) {\n"
	"		int x = int(incoord.x);\n"
	"		incoord.x = float(clamp(x - (mosaic.z + x) % mosaic.x, 0, dims.z - 1));\n"
	"	} else if (mosaic.x < -1) {\n"
	"		int x = dims.z - int(incoord.x) - 1;\n"
	"		incoord.x = float(clamp(dims.z - x + (mosaic.z + x) % -mosaic.x - 1, 0, dims.z - 1));\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		int y = int(incoord.y);\n"
	"		incoord.y = float(clamp(y - (mosaic.w + y) % mosaic.y, 0, dims.w - 1));\n"
	"	}\n"
	"	ivec2 coord = ivec2(transform * (incoord - vec2(dims.zw) / 2.) + vec2(dims.xy) / 2.);\n"
	"	if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n"
	"		discard;\n"
	"	}\n"
	"	vec4 pix = renderTile((coord.x >> 3) + (coord.y >> 3) * stride, localPalette, coord & 7);\n"
	"	color = pix;\n"
	"	flags = inflags;\n"
	"	gl_FragDepth = float(flags.x) / 16.;\n"
	"	window = objwin.yzw;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsWindow[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "dispcnt", GBA_GL_WIN_DISPCNT, },
	{ "blend", GBA_GL_WIN_BLEND, },
	{ "flags", GBA_GL_WIN_FLAGS, },
	{ "win0", GBA_GL_WIN_WIN0, },
	{ "win1", GBA_GL_WIN_WIN1, },
	{ 0 }
};

static const char* const _renderWindow =
	"in vec2 texCoord;\n"
	"uniform int dispcnt;\n"
	"uniform ivec2 blend;\n"
	"uniform ivec3 flags;\n"
	"uniform ivec4 win0[160];\n"
	"uniform ivec4 win1[160];\n"
	"OUT(0) out ivec3 window;\n"

	"void crop(vec4 windowParams, int flags, inout ivec3 windowFlags) {\n"
	"	bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n"
	"	compare = equal(compare, bvec4(true, false, true, false));\n"
	"	if (any(compare)) {\n"
	"		vec2 h = windowParams.xy;\n"
	"		vec2 v = windowParams.zw;\n"
	"		if (v.x > v.y) {\n"
	"			if (compare.z && compare.w) {\n"
	"				return;\n"
	"			}\n"
	"		} else if (compare.z || compare.w) {\n"
	"			return;\n"
	"		}\n"
	"		if (h.x > h.y) {\n"
	"			if (compare.x && compare.y) {\n"
	"				return;\n"
	"			}\n"
	"		} else if (compare.x || compare.y) {\n"
	"			return;\n"
	"		}\n"
	"	}\n"
	"	windowFlags.x = flags;\n"
	"}\n"

	"vec4 interpolate(ivec4 win[160]) {\n"
	"	vec4 bottom = vec4(win[int(texCoord.y) - 1]);\n"
	"	vec4 top = vec4(win[int(texCoord.y)]);\n"
	"	if (distance(top, bottom) > 40.) {\n"
	"		return top;\n"
	"	}\n"
	"	return vec4(mix(bottom.xy, top.xy, fract(texCoord.y)), top.zw);\n"
	"}\n"

	"void main() {\n"
	"	int dispflags = (dispcnt & 0x1F) | 0x20;\n"
	"	if ((dispcnt & 0xE0) == 0) {\n"
	"		window = ivec3(dispflags, blend);\n"
	"	} else {\n"
	"		ivec3 windowFlags = ivec3(flags.z, blend);\n"
	"		if ((dispcnt & 0x40) != 0) { \n"
	"			crop(interpolate(win1), flags.y, windowFlags);\n"
	"		}\n"
	"		if ((dispcnt & 0x20) != 0) { \n"
	"			crop(interpolate(win0), flags.x, windowFlags);\n"
	"		}\n"
	"		window = windowFlags;\n"
	"	}\n"
	"}\n";
<commit_msg>GBA Video: Fix GL output ivec rank<commit_after>/* Copyright (c) 2013-2019 <NAME>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/gba/renderers/gl.h>

#if defined(BUILD_GLES2) || defined(BUILD_GLES3)

#include <mgba/core/cache-set.h>
#include <mgba/internal/arm/macros.h>
#include <mgba/internal/gba/io.h>
#include <mgba/internal/gba/renderers/cache-set.h>
#include <mgba-util/memory.h>

static void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer);
static void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer);
static void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer);
static void GBAVideoGLRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
static void GBAVideoGLRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
static void GBAVideoGLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
static void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer);
static void GBAVideoGLRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels);
static void GBAVideoGLRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);

static void GBAVideoGLRendererUpdateDISPCNT(struct GBAVideoGLRenderer* renderer);
static void GBAVideoGLRendererWriteBGCNT(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBGX_LO(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBGX_HI(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBGY_LO(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBGY_HI(struct GBAVideoGLBackground* bg, uint16_t value);
static void GBAVideoGLRendererWriteBLDCNT(struct GBAVideoGLRenderer* renderer, uint16_t value);

static void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GBAObj* sprite, int y, int spriteY);
static void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawBackgroundMode3(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
static void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y);

static void _cleanRegister(struct GBAVideoGLRenderer* renderer, int address, uint16_t value);
static void _drawScanlines(struct GBAVideoGLRenderer* renderer, int lastY);
static void _finalizeLayers(struct GBAVideoGLRenderer* renderer);

#define TEST_LAYER_ENABLED(X) !glRenderer->d.disableBG[X] && glRenderer->bg[X].enabled == 4

struct GBAVideoGLUniform {
	const char* name;
	int type;
};

#define PALETTE_ENTRY "#define PALETTE_ENTRY(x) (vec3((ivec3(0x1F, 0x3E0, 0x7C00) & (x)) >> ivec3(0, 5, 10)) / 31.)\n"

static const GLchar* const _gles3Header =
	"#version 300 es\n"
	"#define OUT(n) layout(location = n)\n"
	PALETTE_ENTRY
	"precision highp float;\n"
	"precision highp int;\n"
	"precision highp sampler2D;\n"
	"precision highp isampler2D;\n";

static const GLchar* const _gl3Header =
	"#version 130\n"
	"#define OUT(n)\n"
	PALETTE_ENTRY
	"precision highp float;\n";

static const char* const _vertexShader =
	"in vec2 position;\n"
	"uniform ivec2 loc;\n"
	"uniform ivec2 maxPos;\n"
	"out vec2 texCoord;\n"

	"void main() {\n"
	"	vec2 local = vec2(position.x, float(position.y * float(loc.x) + float(loc.y)) / float(maxPos.y));\n"
	"	gl_Position = vec4((local * 2. - 1.) * vec2(sign(maxPos)), 0., 1.);\n"
	"	texCoord = local * vec2(abs(maxPos));\n"
	"}";

static const char* const _renderTile16 =
	"vec4 renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
	"	int address = charBase + tile * 16 + (localCoord.x >> 2) + (localCoord.y << 1);\n"
	"	vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
	"	int entry = int(halfrow[3 - (localCoord.x & 3)] * 15.9);\n"
	"	if (entry == 0) {\n"
	"		discard;\n"
	"	}\n"
	"	int paletteEntry = palette[paletteId * 16 + entry];\n"
	"	vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
	"	return color;\n"
	"}";

static const char* const _renderTile256 =
	"vec4 renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
	"	int address = charBase + tile * 32 + (localCoord.x >> 1) + (localCoord.y << 2);\n"
	"	vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
	"	int entry = int(halfrow[3 - 2 * (localCoord.x & 1)] * 15.9);\n"
	"	int pal2 = int(halfrow[2 - 2 * (localCoord.x & 1)] * 15.9);\n"
	"	if ((pal2 | entry) == 0) {\n"
	"		discard;\n"
	"	}\n"
	"	int paletteEntry = palette[pal2 * 16 + entry];\n"
	"	vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
	"	return color;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsMode0[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_BG_VRAM, },
	{ "palette", GBA_GL_BG_PALETTE, },
	{ "screenBase", GBA_GL_BG_SCREENBASE, },
	{ "charBase", GBA_GL_BG_CHARBASE, },
	{ "size", GBA_GL_BG_SIZE, },
	{ "offset", GBA_GL_BG_OFFSET, },
	{ "inflags", GBA_GL_BG_INFLAGS, },
	{ "mosaic", GBA_GL_BG_MOSAIC, },
	{ 0 }
};

static const char* const _renderMode0 =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int palette[256];\n"
	"uniform int screenBase;\n"
	"uniform int charBase;\n"
	"uniform int size;\n"
	"uniform int offset[160];\n"
	"uniform ivec4 inflags;\n"
	"uniform ivec2 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"

	"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"

	"void main() {\n"
	"	ivec2 coord = ivec2(texCoord);\n"
	"	if (mosaic.x > 1) {\n"
	"		coord.x -= coord.x % mosaic.x;\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		coord.y -= coord.y % mosaic.y;\n"
	"	}\n"
	"	coord += (ivec2(0x3FF, 0x3FF000) & offset[int(texCoord.y)]) >> ivec2(0, 12);\n"
	"	if (size == 3) {\n"
	"		coord.y += (coord.y & 256) << 1;\n"
	"	}\n"
	"	if (size != 2) {\n"
	"		coord.y &= ~256;\n"
	"	}\n"
	"	if ((size & 1) == 1) {\n"
	"		coord.y += coord.x & 256;\n"
	"	}\n"
	"	coord &= ivec2(255, 511);\n"
	"	int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n"
	"	vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
	"	int tileFlags = int(map.g * 15.9);\n"
	"	if ((tileFlags & 4) == 4) {\n"
	"		coord.x ^= 7;\n"
	"	}\n"
	"	if ((tileFlags & 8) == 8) {\n"
	"		coord.y ^= 7;\n"
	"	}\n"
	"	int tile = int(map.a * 15.9) + int(map.b * 15.9) * 16 + (tileFlags & 0x3) * 256;\n"
	"	color = renderTile(tile, int(map.r * 15.9), coord & 7);\n"
	"	flags = inflags;\n"
	"}";

static const char* const _fetchTileOverflow =
	"vec4 fetchTile(ivec2 coord) {\n"
	"	int sizeAdjusted = (0x8000 << size) - 1;\n"
	"	coord &= sizeAdjusted;\n"
	"	return renderTile(coord);\n"
	"}";

static const char* const _fetchTileNoOverflow =
	"vec4 fetchTile(ivec2 coord) {\n"
	"	int sizeAdjusted = (0x8000 << size) - 1;\n"
	"	ivec2 outerCoord = coord & ~sizeAdjusted;\n"
	"	if ((outerCoord.x | outerCoord.y) != 0) {\n"
	"		discard;\n"
	"	}\n"
	"	return renderTile(coord);\n"
	"}";

static const struct GBAVideoGLUniform _uniformsMode2[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_BG_VRAM, },
	{ "palette", GBA_GL_BG_PALETTE, },
	{ "screenBase", GBA_GL_BG_SCREENBASE, },
	{ "charBase", GBA_GL_BG_CHARBASE, },
	{ "size", GBA_GL_BG_SIZE, },
	{ "inflags", GBA_GL_BG_INFLAGS, },
	{ "offset", GBA_GL_BG_OFFSET, },
	{ "transform", GBA_GL_BG_TRANSFORM, },
	{ "range", GBA_GL_BG_RANGE, },
	{ "mosaic", GBA_GL_BG_MOSAIC, },
	{ 0 }
};

static const char* const _interpolate =
	"vec2 interpolate(ivec2 arr[4], float x) {\n"
	"	float x1m = 1. - x;\n"
	"	return x1m * x1m * x1m * vec2(arr[0]) +"
		" 3. * x1m * x1m * x   * vec2(arr[1]) +"
		" 3. * x1m * x   * x   * vec2(arr[2]) +"
		"      x   * x   * x   * vec2(arr[3]);\n"
	"}\n"

	"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]) {\n"
	"	int start = max(range.x, y - 3);\n"
	"	mat[0] = transform[start + 0].xy;\n"
	"	aff[0] = transform[start + 0].zw;\n"
	"	mat[1] = transform[start + 1].xy;\n"
	"	aff[1] = transform[start + 1].zw;\n"
	"	mat[2] = transform[start + 2].xy;\n"
	"	aff[2] = transform[start + 2].zw;\n"
	"	mat[3] = transform[start + 3].xy;\n"
	"	aff[3] = transform[start + 3].zw;\n"
	"}\n";

static const char* const _renderMode2 =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int palette[256];\n"
	"uniform int screenBase;\n"
	"uniform int charBase;\n"
	"uniform int size;\n"
	"uniform ivec4 inflags;\n"
	"uniform ivec4 transform[160];\n"
	"uniform ivec2 range;\n"
	"uniform ivec2 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"

	"vec4 fetchTile(ivec2 coord);\n"
	"vec2 interpolate(ivec2 arr[4], float x);\n"
	"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"

	"vec4 renderTile(ivec2 coord) {\n"
	"	int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n"
	"	int mapAddress = screenBase + (map >> 1);\n"
	"	vec4 twomaps = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
	"	int tile = int(twomaps[3 - 2 * (map & 1)] * 15.9) + int(twomaps[2 - 2 * (map & 1)] * 15.9) * 16;\n"
	"	int address = charBase + tile * 32 + ((coord.x >> 9) & 3) + ((coord.y >> 6) & 0x1C);\n"
	"	vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
	"	int entry = int(halfrow[3 - ((coord.x >> 7) & 2)] * 15.9);\n"
	"	int pal2 = int(halfrow[2 - ((coord.x >> 7) & 2)] * 15.9);\n"
	"	if ((pal2 | entry) == 0) {\n"
	"		discard;\n"
	"	}\n"
	"	int paletteEntry = palette[pal2 * 16 + entry];\n"
	"	vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
	"	return color;\n"
	"}\n"

	"void main() {\n"
	"	ivec2 mat[4];\n"
	"	ivec2 offset[4];\n"
	"	vec2 incoord = texCoord;\n"
	"	if (mosaic.x > 1) {\n"
	"		incoord.x = float(int(incoord.x) % mosaic.x);\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		incoord.y = float(int(incoord.y) % mosaic.y);\n"
	"	}\n"
	"	loadAffine(int(incoord.y), mat, offset);\n"
	"	float y = fract(incoord.y);\n"
	"	float start = 0.75;\n"
	"	if (int(incoord.y) - range.x < 4) {\n"
	"		y = incoord.y - float(range.x);\n"
	"		start = 0.;\n"
	"	}\n"
	"	float lin = start + y * 0.25;\n"
	"	vec2 mixedTransform = interpolate(mat, lin);\n"
	"	vec2 mixedOffset = interpolate(offset, lin);\n"
	"	color = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n"
	"	flags = inflags;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsMode35[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_BG_VRAM, },
	{ "charBase", GBA_GL_BG_CHARBASE, },
	{ "size", GBA_GL_BG_SIZE, },
	{ "inflags", GBA_GL_BG_INFLAGS, },
	{ "offset", GBA_GL_BG_OFFSET, },
	{ "transform", GBA_GL_BG_TRANSFORM, },
	{ "range", GBA_GL_BG_RANGE, },
	{ "mosaic", GBA_GL_BG_MOSAIC, },
	{ 0 }
};

static const char* const _renderMode35 =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int charBase;\n"
	"uniform ivec2 size;\n"
	"uniform ivec4 inflags;\n"
	"uniform ivec4 transform[160];\n"
	"uniform ivec2 range;\n"
	"uniform ivec2 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"

	"vec2 interpolate(ivec2 arr[4], float x);\n"
	"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"

	"void main() {\n"
	"	ivec2 mat[4];\n"
	"	ivec2 offset[4];\n"
	"	vec2 incoord = texCoord;\n"
	"	if (mosaic.x > 1) {\n"
	"		incoord.x = float(int(incoord.x) % mosaic.x);\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		incoord.y = float(int(incoord.y) % mosaic.y);\n"
	"	}\n"
	"	loadAffine(int(incoord.y), mat, offset);\n"
	"	float y = fract(incoord.y);\n"
	"	float start = 0.75;\n"
	"	if (int(incoord.y) - range.x < 4) {\n"
	"		y = incoord.y - float(range.x);\n"
	"		start = 0.;\n"
	"	}\n"
	"	float lin = start + y * 0.25;\n"
	"	vec2 mixedTransform = interpolate(mat, lin);\n"
	"	vec2 mixedOffset = interpolate(offset, lin);\n"
	"	ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n"
	"	if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
	"		discard;\n"
	"	}\n"
	"	if (coord.y < 0 || coord.y >= (size.y << 8)) {\n"
	"		discard;\n"
	"	}\n"
	"	int address = charBase + (coord.x >> 8) + (coord.y >> 8) * size.x;\n"
	"	ivec4 entry = ivec4(texelFetch(vram, ivec2(address & 255, address >> 8), 0) * 15.9);\n"
	"	int sixteen = (entry.x << 12) | (entry.y << 8) | (entry.z << 4) | entry.w;\n"
	"	color = vec4(float(sixteen & 0x1F) / 31., float((sixteen >> 5) & 0x1F) / 31., float((sixteen >> 10) & 0x1F) / 31., 1.);\n"
	"	flags = inflags;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsMode4[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_BG_VRAM, },
	{ "palette", GBA_GL_BG_PALETTE, },
	{ "charBase", GBA_GL_BG_CHARBASE, },
	{ "size", GBA_GL_BG_SIZE, },
	{ "inflags", GBA_GL_BG_INFLAGS, },
	{ "offset", GBA_GL_BG_OFFSET, },
	{ "transform", GBA_GL_BG_TRANSFORM, },
	{ "range", GBA_GL_BG_RANGE, },
	{ "mosaic", GBA_GL_BG_MOSAIC, },
	{ 0 }
};

static const char* const _renderMode4 =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int palette[256];\n"
	"uniform int charBase;\n"
	"uniform ivec2 size;\n"
	"uniform ivec4 inflags;\n"
	"uniform ivec4 transform[160];\n"
	"uniform ivec2 range;\n"
	"uniform ivec2 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"

	"vec2 interpolate(ivec2 arr[4], float x);\n"
	"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"

	"void main() {\n"
	"	ivec2 mat[4];\n"
	"	ivec2 offset[4];\n"
	"	vec2 incoord = texCoord;\n"
	"	if (mosaic.x > 1) {\n"
	"		incoord.x = float(int(incoord.x) % mosaic.x);\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		incoord.y = float(int(incoord.y) % mosaic.y);\n"
	"	}\n"
	"	loadAffine(int(incoord.y), mat, offset);\n"
	"	float y = fract(incoord.y);\n"
	"	float start = 0.75;\n"
	"	if (int(incoord.y) - range.x < 4) {\n"
	"		y = incoord.y - float(range.x);\n"
	"		start = 0.;\n"
	"	}\n"
	"	float lin = start + y * 0.25;\n"
	"	vec2 mixedTransform = interpolate(mat, lin);\n"
	"	vec2 mixedOffset = interpolate(offset, lin);\n"
	"	ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n"
	"	if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
	"		discard;\n"
	"	}\n"
	"	if (coord.y < 0 || coord.y >= (size.y << 8)) {\n"
	"		discard;\n"
	"	}\n"
	"	int address = charBase + (coord.x >> 8) + (coord.y >> 8) * size.x;\n"
	"	vec4 twoEntries = texelFetch(vram, ivec2((address >> 1) & 255, address >> 9), 0);\n"
	"	ivec2 entry = ivec2(twoEntries[3 - 2 * (address & 1)] * 15.9, twoEntries[2 - 2 * (address & 1)] * 15.9);\n"
	"	int paletteEntry = palette[entry.y * 16 + entry.x];\n"
	"	color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
	"	flags = inflags;\n"
	"}";

static const struct GBAVideoGLUniform _uniformsObj[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "vram", GBA_GL_OBJ_VRAM, },
	{ "palette", GBA_GL_OBJ_PALETTE, },
	{ "charBase", GBA_GL_OBJ_CHARBASE, },
	{ "stride", GBA_GL_OBJ_STRIDE, },
	{ "localPalette", GBA_GL_OBJ_LOCALPALETTE, },
	{ "inflags", GBA_GL_OBJ_INFLAGS, },
	{ "transform", GBA_GL_OBJ_TRANSFORM, },
	{ "dims", GBA_GL_OBJ_DIMS, },
	{ "objwin", GBA_GL_OBJ_OBJWIN, },
	{ "mosaic", GBA_GL_OBJ_MOSAIC, },
	{ 0 }
};

static const char* const _renderObj =
	"in vec2 texCoord;\n"
	"uniform sampler2D vram;\n"
	"uniform int palette[256];\n"
	"uniform int charBase;\n"
	"uniform int stride;\n"
	"uniform int localPalette;\n"
	"uniform ivec4 inflags;\n"
	"uniform mat2x2 transform;\n"
	"uniform ivec4 dims;\n"
	"uniform ivec4 objwin;\n"
	"uniform ivec4 mosaic;\n"
	"OUT(0) out vec4 color;\n"
	"OUT(1) out ivec4 flags;\n"
	"OUT(2) out ivec4 window;\n"

	"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"

	"void main() {\n"
	"	vec2 incoord = texCoord;\n"
	"	if (mosaic.x > 1) {\n"
	"		int x = int(incoord.x);\n"
	"		incoord.x = float(clamp(x - (mosaic.z + x) % mosaic.x, 0, dims.z - 1));\n"
	"	} else if (mosaic.x < -1) {\n"
	"		int x = dims.z - int(incoord.x) - 1;\n"
	"		incoord.x = float(clamp(dims.z - x + (mosaic.z + x) % -mosaic.x - 1, 0, dims.z - 1));\n"
	"	}\n"
	"	if (mosaic.y > 1) {\n"
	"		int y = int(incoord.y);\n"
	"		incoord.y = float(clamp(y - (mosaic.w + y) % mosaic.y, 0, dims.w - 1));\n"
	"	}\n"
	"	ivec2 coord = ivec2(transform * (incoord - vec2(dims.zw) / 2.) + vec2(dims.xy) / 2.);\n"
	"	if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n"
	"		discard;\n"
	"	}\n"
	"	vec4 pix = renderTile((coord.x >> 3) + (coord.y >> 3) * stride, localPalette, coord & 7);\n"
	"	color = pix;\n"
	"	flags = inflags;\n"
	"	gl_FragDepth = float(flags.x) / 16.;\n"
	"	window = ivec4(objwin.yzw, 0);\n"
	"}";

static const struct GBAVideoGLUniform _uniformsWindow[] = {
	{ "loc", GBA_GL_VS_LOC, },
	{ "maxPos", GBA_GL_VS_MAXPOS, },
	{ "dispcnt", GBA_GL_WIN_DISPCNT, },
	{ "blend", GBA_GL_WIN_BLEND, },
	{ "flags", GBA_GL_WIN_FLAGS, },
	{ "win0", GBA_GL_WIN_WIN0, },
	{ "win1", GBA_GL_WIN_WIN1, },
	{ 0 }
};

static const char* const _renderWindow =
	"in vec2 texCoord;\n"
	"uniform int dispcnt;\n"
	"uniform ivec2 blend;\n"
	"uniform ivec3 flags;\n"
	"uniform ivec4 win0[160];\n"
	"uniform ivec4 win1[160];\n"
	"OUT(0) out ivec4 window;\n"

	"void crop(vec4 windowParams, int flags, inout ivec3 windowFlags) {\n"
	"	bvec4 compare = lessThan(texCoord.xxyy, windowParams);\n"
	"	compare = equal(compare, bvec4(true, false, true, false));\n"
	"	if (any(compare)) {\n"
	"		vec2 h = windowParams.xy;\n"
	"		vec2 v = windowParams.zw;\n"
	"		if (v.x > v.y) {\n"
	"			if (compare.z && compare.w) {\n"
	"				return;\n"
	"			}\n"
	"		} else if (compare.z || compare.w) {\n"
	"			return;\n"
	"		}\n"
	"		if (h.x > h.y) {\n"
	"			if (compare.x && compare.y) {\n"
	"				return;\n"
	"			}\n"
	"		} else if (compare.x || compare.y) {\n"
	"			return;\n"
	"		}\n"
	"	}\n"
	"	windowFlags.x = flags;\n"
	"}\n"

	"vec4 interpolate(ivec4 win[160]) {\n"
	"	vec4 bottom = vec4(win[int(texCoord.y) - 1]);\n"
	"	vec4 top = vec4(win[int(texCoord.y)]);\n"
	"	if (distance(top, bottom) > 40.) {\n"
	"		return top;\n"
	"	}\n"
	"	return vec4(mix(bottom.xy, top.xy, fract(texCoord.y)), top.zw);\n"
	"}\n"

	"void main() {\n"
	"	int dispflags = (dispcnt & 0x1F) | 0x20;\n"
	"	if ((dispcnt & 0xE0) == 0) {\n"
	"		window = ivec4(dispflags, blend, 0);\n"
	"	} else {\n"
	"		ivec3 windowFlags = ivec3(flags.z, blend);\n"
	"		if ((dispcnt & 0x40) != 0) { \n"
	"			crop(interpolate(win1), flags.y, windowFlags);\n"
	"		}\n"
	"		if ((dispcnt & 0x20) != 0) { \n"
	"			crop(interpolate(win0), flags.x, windowFlags);\n"
	"		}\n"
	"		window = ivec4(windowFlags, 0);\n"
	"	}\n"
	"}\n";

{'fraction_non_alphanumeric': 0.19299984351364038, 'fraction_numerical': 0.03586145740962913, 'mean_word_length': 3.9718620331950207, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 171, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5411912', 'n_tokens_mistral': 18150, 'n_tokens_neox': 17082, 'n_words': 3605}