mirror of https://github.com/mgba-emu/mgba.git
GBA Video: Add special circlular window handling in OpenGL renderer
This commit is contained in:
parent
c7b5d10546
commit
b7729c9e80
1
CHANGES
1
CHANGES
|
@ -32,6 +32,7 @@ Misc:
|
|||
- GB Serialize: Add missing savestate support for MBC6 and NT (newer)
|
||||
- GBA: Improve detection of valid ELF ROMs
|
||||
- GBA Audio: Remove broken XQ audio pending rewrite
|
||||
- GBA Video: Add special circlular window handling in OpenGL renderer
|
||||
- Libretro: Add Super Game Boy Color support (closes mgba.io/i/3188)
|
||||
- mGUI: Enable auto-softpatching (closes mgba.io/i/2899)
|
||||
- mGUI: Persist fast forwarding after closing menu (fixes mgba.io/i/2414)
|
||||
|
|
|
@ -118,6 +118,8 @@ enum {
|
|||
GBA_GL_WIN_FLAGS,
|
||||
GBA_GL_WIN_WIN0,
|
||||
GBA_GL_WIN_WIN1,
|
||||
GBA_GL_WIN_CIRCLE0,
|
||||
GBA_GL_WIN_CIRCLE1,
|
||||
|
||||
GBA_GL_FINALIZE_SCALE = 2,
|
||||
GBA_GL_FINALIZE_LAYERS,
|
||||
|
|
|
@ -486,6 +486,8 @@ static const struct GBAVideoGLUniform _uniformsWindow[] = {
|
|||
{ "flags", GBA_GL_WIN_FLAGS, },
|
||||
{ "win0", GBA_GL_WIN_WIN0, },
|
||||
{ "win1", GBA_GL_WIN_WIN1, },
|
||||
{ "circle0", GBA_GL_WIN_CIRCLE0, },
|
||||
{ "circle1", GBA_GL_WIN_CIRCLE1, },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
@ -496,6 +498,8 @@ static const char* const _renderWindow =
|
|||
"uniform ivec3 flags;\n"
|
||||
"uniform ivec4 win0[160];\n"
|
||||
"uniform ivec4 win1[160];\n"
|
||||
"uniform vec3 circle0;\n"
|
||||
"uniform vec3 circle1;\n"
|
||||
"OUT(0) out ivec4 window;\n"
|
||||
|
||||
"bool crop(vec4 windowParams) {\n"
|
||||
|
@ -529,13 +533,20 @@ static const char* const _renderWindow =
|
|||
" return vec4(mix(bottom.xy, top.xy, fract(texCoord.y)), top.zw);\n"
|
||||
"}\n"
|
||||
|
||||
"bool test(vec3 circle, vec4 top, vec4 bottom) {\n"
|
||||
" if (circle.z > 0) {\n"
|
||||
" return distance(circle.xy, texCoord.xy) <= circle.z;\n"
|
||||
" }\n"
|
||||
" return crop(interpolate(top, bottom));\n"
|
||||
"}\n"
|
||||
|
||||
"void main() {\n"
|
||||
" ivec4 windowFlags = ivec4(flags.z, blend, 0);\n"
|
||||
" int top = int(texCoord.y);\n"
|
||||
" int bottom = max(top - 1, 0);\n"
|
||||
" if ((dispcnt & 0x20) != 0 && crop(interpolate(vec4(win0[top]), vec4(win0[bottom])))) { \n"
|
||||
" if ((dispcnt & 0x20) != 0 && test(circle0, vec4(win0[top]), vec4(win0[bottom]))) {\n"
|
||||
" windowFlags.x = flags.x;\n"
|
||||
" } else if ((dispcnt & 0x40) != 0 && crop(interpolate(vec4(win1[top]), vec4(win1[bottom])))) {\n"
|
||||
" } else if ((dispcnt & 0x40) != 0 && test(circle1, vec4(win1[top]), vec4(win1[bottom]))) {\n"
|
||||
" windowFlags.x = flags.y;\n"
|
||||
" }\n"
|
||||
" window = windowFlags;\n"
|
||||
|
@ -1924,6 +1935,127 @@ void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer,
|
|||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
}
|
||||
|
||||
static void _detectCircle(struct GBAVideoGLRenderer* renderer, int y, int window) {
|
||||
int lastStart = 0;
|
||||
int lastEnd = 0;
|
||||
|
||||
int startX = 0;
|
||||
int endX = 0;
|
||||
|
||||
int firstY = -1;
|
||||
float centerX;
|
||||
float centerY = -1;
|
||||
float radius = 0;
|
||||
bool invalid = false;
|
||||
|
||||
int i;
|
||||
for (i = renderer->firstY; i <= y; ++i) {
|
||||
lastStart = startX;
|
||||
lastEnd = endX;
|
||||
startX = renderer->winNHistory[window][i * 4];
|
||||
endX = renderer->winNHistory[window][i * 4 + 1];
|
||||
int startY = renderer->winNHistory[window][i * 4 + 2];
|
||||
int endY = renderer->winNHistory[window][i * 4 + 3];
|
||||
|
||||
if (startX == endX || i < startY || i >= endY) {
|
||||
if (firstY >= 0) {
|
||||
// The bottom edge of the circle
|
||||
centerY = (firstY + i) / 2.f;
|
||||
firstY = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (lastEnd - lastStart <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The previous segment was non-zero
|
||||
if (startX >= GBA_VIDEO_HORIZONTAL_PIXELS) {
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int startDiff = lastStart - startX;
|
||||
int endDiff = endX - lastEnd;
|
||||
// Make sure the slopes match, otherwise this isn't a circle
|
||||
if (startDiff - endDiff < -1 || startDiff - endDiff > 1) {
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (startX < lastStart) {
|
||||
centerX = (startX + endX) / 2.f;
|
||||
if (radius > 0) {
|
||||
// We found two separate shapes, which the interpolation can't handle
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
} else if (startX > lastStart && radius <= 0) {
|
||||
radius = (lastEnd - lastStart) / 2.f;
|
||||
}
|
||||
|
||||
if (firstY < 0 && i - 1 >= startY && i - 1 < endY) {
|
||||
firstY = i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (radius <= 0) {
|
||||
invalid = true;
|
||||
}
|
||||
if (centerY < 0) {
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
// Check validity
|
||||
for (i = renderer->firstY; i <= y && !invalid; ++i) {
|
||||
int startX = renderer->winNHistory[window][i * 4];
|
||||
int endX = renderer->winNHistory[window][i * 4 + 1];
|
||||
int startY = renderer->winNHistory[window][i * 4 + 2];
|
||||
int endY = renderer->winNHistory[window][i * 4 + 3];
|
||||
|
||||
bool xActive = startX < endX;
|
||||
bool yActive = i >= startY && i < endY;
|
||||
|
||||
if (xActive && yActive) {
|
||||
// Real window would be active, make sure simulated window would too
|
||||
if (centerY - i > radius) {
|
||||
// y is above the radius
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
if (i - centerY > radius) {
|
||||
// y is below the radius
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
float cosine = fabsf(i - centerY);
|
||||
float sine = sqrtf(radius * radius - cosine * cosine);
|
||||
if (fabsf(centerX - sine - startX) <= 1 && fabsf(centerX + sine - endX) <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (radius >= cosine + 1) {
|
||||
sine = sqrtf(radius * radius - (cosine + 1) * (cosine + 1));
|
||||
if (fabsf(centerX - sine - startX) <= 1 && fabsf(centerX + sine - endX) <= 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// y is active on the wrong parts of the scanline
|
||||
invalid = true;
|
||||
} else if (centerY - i < radius && i - centerY < radius) {
|
||||
// Real window would be inactive, make sure simulated window would too
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalid) {
|
||||
glUniform3f(renderer->windowShader.uniforms[GBA_GL_WIN_CIRCLE0 + window], 0, 0, 0);
|
||||
} else {
|
||||
glUniform3f(renderer->windowShader.uniforms[GBA_GL_WIN_CIRCLE0 + window], centerX, centerY, radius - 0.499);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) {
|
||||
const struct GBAVideoGLShader* shader = &renderer->windowShader;
|
||||
const GLuint* uniforms = shader->uniforms;
|
||||
|
@ -1950,6 +2082,8 @@ void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) {
|
|||
glUniform3i(uniforms[GBA_GL_WIN_FLAGS], renderer->winN[0].control, renderer->winN[1].control, renderer->winout);
|
||||
glUniform4iv(uniforms[GBA_GL_WIN_WIN0], GBA_VIDEO_VERTICAL_PIXELS, renderer->winNHistory[0]);
|
||||
glUniform4iv(uniforms[GBA_GL_WIN_WIN1], GBA_VIDEO_VERTICAL_PIXELS, renderer->winNHistory[1]);
|
||||
_detectCircle(renderer, y, 0);
|
||||
_detectCircle(renderer, y, 1);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue