[Glide64] add (optional) GLES 2.0 support, code taken from mupen64plus-ae
ee42abc424
This commit is contained in:
parent
5c1f6ea6c6
commit
e5c6fa245b
|
@ -145,23 +145,23 @@
|
|||
Filter="*.cpp"
|
||||
>
|
||||
<File
|
||||
RelativePath="combiner.cpp"
|
||||
RelativePath=".\OGLcombiner.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="geometry.cpp"
|
||||
RelativePath=".\OGLgeometry.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="main.cpp"
|
||||
RelativePath=".\OGLglitchmain.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="textures.cpp"
|
||||
RelativePath=".\OGLtextures.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="vram.cpp"
|
||||
RelativePath=".\vram.cpp"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,643 @@
|
|||
/*
|
||||
* Glide64 - Glide video plugin for Nintendo 64 emulators.
|
||||
* Copyright (c) 2002 Dave2001
|
||||
* Copyright (c) 2003-2009 Sergey 'Gonetz' Lipski
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif // _WIN32
|
||||
#include "glide.h"
|
||||
#include "main.h"
|
||||
#include "../Glide64/winlnxdefs.h"
|
||||
#include "../Glide64/rdp.h"
|
||||
|
||||
#define Z_MAX (65536.0f)
|
||||
#define VERTEX_SIZE sizeof(VERTEX) //Size of vertex struct
|
||||
|
||||
#ifdef ANDROID_EDITION
|
||||
#include "ae_imports.h"
|
||||
static float polygonOffsetFactor;
|
||||
static float polygonOffsetUnits;
|
||||
#endif
|
||||
|
||||
static int xy_off;
|
||||
static int xy_en;
|
||||
static int z_en;
|
||||
static int z_off;
|
||||
static int q_off;
|
||||
static int q_en;
|
||||
static int pargb_off;
|
||||
static int pargb_en;
|
||||
static int st0_off;
|
||||
static int st0_en;
|
||||
static int st1_off;
|
||||
static int st1_en;
|
||||
static int fog_ext_off;
|
||||
static int fog_ext_en;
|
||||
|
||||
int w_buffer_mode;
|
||||
int inverted_culling;
|
||||
int culling_mode;
|
||||
|
||||
#define VERTEX_BUFFER_SIZE 1500 //Max amount of vertices to buffer, this seems large enough.
|
||||
static VERTEX vertex_buffer[VERTEX_BUFFER_SIZE];
|
||||
static int vertex_buffer_count = 0;
|
||||
static GLenum vertex_draw_mode;
|
||||
static bool vertex_buffer_enabled = false;
|
||||
|
||||
void vbo_init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void vbo_draw()
|
||||
{
|
||||
if(vertex_buffer_count)
|
||||
{
|
||||
glDrawArrays(vertex_draw_mode,0,vertex_buffer_count);
|
||||
vertex_buffer_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//Buffer vertices instead of glDrawArrays(...)
|
||||
void vbo_buffer(GLenum mode,GLint first,GLsizei count,void* pointers)
|
||||
{
|
||||
if((count != 3 && mode != GL_TRIANGLES) || vertex_buffer_count + count > VERTEX_BUFFER_SIZE)
|
||||
{
|
||||
vbo_draw();
|
||||
}
|
||||
|
||||
memcpy(&vertex_buffer[vertex_buffer_count],pointers,count * VERTEX_SIZE);
|
||||
vertex_buffer_count += count;
|
||||
|
||||
if(count == 3 || mode == GL_TRIANGLES)
|
||||
{
|
||||
vertex_draw_mode = GL_TRIANGLES;
|
||||
}
|
||||
else
|
||||
{
|
||||
vertex_draw_mode = mode;
|
||||
vbo_draw(); //Triangle fans and strips can't be joined as easily, just draw them straight away.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void vbo_enable()
|
||||
{
|
||||
if(vertex_buffer_enabled)
|
||||
return;
|
||||
|
||||
vertex_buffer_enabled = true;
|
||||
glEnableVertexAttribArray(POSITION_ATTR);
|
||||
glVertexAttribPointer(POSITION_ATTR, 4, GL_FLOAT, false, VERTEX_SIZE, &vertex_buffer[0].x); //Position
|
||||
|
||||
glEnableVertexAttribArray(COLOUR_ATTR);
|
||||
glVertexAttribPointer(COLOUR_ATTR, 4, GL_UNSIGNED_BYTE, true, VERTEX_SIZE, &vertex_buffer[0].b); //Colour
|
||||
|
||||
glEnableVertexAttribArray(TEXCOORD_0_ATTR);
|
||||
glVertexAttribPointer(TEXCOORD_0_ATTR, 2, GL_FLOAT, false, VERTEX_SIZE, &vertex_buffer[0].coord[2]); //Tex0
|
||||
|
||||
glEnableVertexAttribArray(TEXCOORD_1_ATTR);
|
||||
glVertexAttribPointer(TEXCOORD_1_ATTR, 2, GL_FLOAT, false, VERTEX_SIZE, &vertex_buffer[0].coord[0]); //Tex1
|
||||
|
||||
glEnableVertexAttribArray(FOG_ATTR);
|
||||
glVertexAttribPointer(FOG_ATTR, 1, GL_FLOAT, false, VERTEX_SIZE, &vertex_buffer[0].f); //Fog
|
||||
}
|
||||
|
||||
void vbo_disable()
|
||||
{
|
||||
vbo_draw();
|
||||
vertex_buffer_enabled = false;
|
||||
}
|
||||
|
||||
|
||||
inline float ZCALC(const float & z, const float & q) {
|
||||
float res = z_en ? ((z) / Z_MAX) / (q) : 1.0f;
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
#define zclamp (1.0f-1.0f/zscale)
|
||||
static inline void zclamp_glVertex4f(float a, float b, float c, float d)
|
||||
{
|
||||
if (c<zclamp) c = zclamp;
|
||||
glVertex4f(a,b,c,d);
|
||||
}
|
||||
#define glVertex4f(a,b,c,d) zclamp_glVertex4f(a,b,c,d)
|
||||
*/
|
||||
|
||||
|
||||
static inline float ytex(int tmu, float y) {
|
||||
if (invtex[tmu])
|
||||
return invtex[tmu] - y;
|
||||
else
|
||||
return y;
|
||||
}
|
||||
|
||||
void init_geometry()
|
||||
{
|
||||
xy_en = q_en = pargb_en = st0_en = st1_en = z_en = 0;
|
||||
w_buffer_mode = 0;
|
||||
inverted_culling = 0;
|
||||
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
vbo_init();
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grCoordinateSpace( GrCoordinateSpaceMode_t mode )
|
||||
{
|
||||
LOG("grCoordinateSpace(%d)\r\n", mode);
|
||||
switch(mode)
|
||||
{
|
||||
case GR_WINDOW_COORDS:
|
||||
break;
|
||||
default:
|
||||
display_warning("unknwown coordinate space : %x", mode);
|
||||
}
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grVertexLayout(FxU32 param, FxI32 offset, FxU32 mode)
|
||||
{
|
||||
LOG("grVertexLayout(%d,%d,%d)\r\n", param, offset, mode);
|
||||
switch(param)
|
||||
{
|
||||
case GR_PARAM_XY:
|
||||
xy_en = mode;
|
||||
xy_off = offset;
|
||||
break;
|
||||
case GR_PARAM_Z:
|
||||
z_en = mode;
|
||||
z_off = offset;
|
||||
break;
|
||||
case GR_PARAM_Q:
|
||||
q_en = mode;
|
||||
q_off = offset;
|
||||
break;
|
||||
case GR_PARAM_FOG_EXT:
|
||||
fog_ext_en = mode;
|
||||
fog_ext_off = offset;
|
||||
break;
|
||||
case GR_PARAM_PARGB:
|
||||
pargb_en = mode;
|
||||
pargb_off = offset;
|
||||
break;
|
||||
case GR_PARAM_ST0:
|
||||
st0_en = mode;
|
||||
st0_off = offset;
|
||||
break;
|
||||
case GR_PARAM_ST1:
|
||||
st1_en = mode;
|
||||
st1_off = offset;
|
||||
break;
|
||||
default:
|
||||
display_warning("unknown grVertexLayout parameter : %x", param);
|
||||
}
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grCullMode( GrCullMode_t mode )
|
||||
{
|
||||
LOG("grCullMode(%d)\r\n", mode);
|
||||
static int oldmode = -1, oldinv = -1;
|
||||
culling_mode = mode;
|
||||
if (inverted_culling == oldinv && oldmode == mode)
|
||||
return;
|
||||
oldmode = mode;
|
||||
oldinv = inverted_culling;
|
||||
switch(mode)
|
||||
{
|
||||
case GR_CULL_DISABLE:
|
||||
glDisable(GL_CULL_FACE);
|
||||
break;
|
||||
case GR_CULL_NEGATIVE:
|
||||
if (!inverted_culling)
|
||||
glCullFace(GL_FRONT);
|
||||
else
|
||||
glCullFace(GL_BACK);
|
||||
glEnable(GL_CULL_FACE);
|
||||
break;
|
||||
case GR_CULL_POSITIVE:
|
||||
if (!inverted_culling)
|
||||
glCullFace(GL_BACK);
|
||||
else
|
||||
glCullFace(GL_FRONT);
|
||||
glEnable(GL_CULL_FACE);
|
||||
break;
|
||||
default:
|
||||
display_warning("unknown cull mode : %x", mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Depth buffer
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDepthBufferMode( GrDepthBufferMode_t mode )
|
||||
{
|
||||
LOG("grDepthBufferMode(%d)\r\n", mode);
|
||||
switch(mode)
|
||||
{
|
||||
case GR_DEPTHBUFFER_DISABLE:
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
w_buffer_mode = 0;
|
||||
return;
|
||||
case GR_DEPTHBUFFER_WBUFFER:
|
||||
case GR_DEPTHBUFFER_WBUFFER_COMPARE_TO_BIAS:
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
w_buffer_mode = 1;
|
||||
break;
|
||||
case GR_DEPTHBUFFER_ZBUFFER:
|
||||
case GR_DEPTHBUFFER_ZBUFFER_COMPARE_TO_BIAS:
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
w_buffer_mode = 0;
|
||||
break;
|
||||
default:
|
||||
display_warning("unknown depth buffer mode : %x", mode);
|
||||
}
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDepthBufferFunction( GrCmpFnc_t function )
|
||||
{
|
||||
LOG("grDepthBufferFunction(%d)\r\n", function);
|
||||
switch(function)
|
||||
{
|
||||
case GR_CMP_GEQUAL:
|
||||
if (w_buffer_mode)
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
else
|
||||
glDepthFunc(GL_GEQUAL);
|
||||
break;
|
||||
case GR_CMP_LEQUAL:
|
||||
if (w_buffer_mode)
|
||||
glDepthFunc(GL_GEQUAL);
|
||||
else
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
break;
|
||||
case GR_CMP_LESS:
|
||||
if (w_buffer_mode)
|
||||
glDepthFunc(GL_GREATER);
|
||||
else
|
||||
glDepthFunc(GL_LESS);
|
||||
break;
|
||||
case GR_CMP_ALWAYS:
|
||||
glDepthFunc(GL_ALWAYS);
|
||||
break;
|
||||
case GR_CMP_EQUAL:
|
||||
glDepthFunc(GL_EQUAL);
|
||||
break;
|
||||
case GR_CMP_GREATER:
|
||||
if (w_buffer_mode)
|
||||
glDepthFunc(GL_LESS);
|
||||
else
|
||||
glDepthFunc(GL_GREATER);
|
||||
break;
|
||||
case GR_CMP_NEVER:
|
||||
glDepthFunc(GL_NEVER);
|
||||
break;
|
||||
case GR_CMP_NOTEQUAL:
|
||||
glDepthFunc(GL_NOTEQUAL);
|
||||
break;
|
||||
|
||||
default:
|
||||
display_warning("unknown depth buffer function : %x", function);
|
||||
}
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDepthMask( FxBool mask )
|
||||
{
|
||||
LOG("grDepthMask(%d)\r\n", mask);
|
||||
glDepthMask(mask);
|
||||
}
|
||||
float biasFactor = 0;
|
||||
#if 0
|
||||
void FindBestDepthBias()
|
||||
{
|
||||
#ifdef ANDROID_EDITION
|
||||
int hardwareType = Android_JNI_GetHardwareType();
|
||||
Android_JNI_GetPolygonOffset(hardwareType, 1, &polygonOffsetFactor, &polygonOffsetUnits);
|
||||
#else
|
||||
float f, bestz = 0.25f;
|
||||
int x;
|
||||
if (biasFactor) return;
|
||||
biasFactor = 64.0f; // default value
|
||||
glPushAttrib(GL_ALL_ATTRIB_BITS);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_ALWAYS);
|
||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
glDrawBuffer(GL_BACK);
|
||||
glReadBuffer(GL_BACK);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
glColor4ub(255,255,255,255);
|
||||
glDepthMask(GL_TRUE);
|
||||
for (x=0, f=1.0f; f<=65536.0f; x+=4, f*=2.0f) {
|
||||
float z;
|
||||
glPolygonOffset(0, f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glVertex3f(float(x+4 - widtho)/(width/2), float(0 - heighto)/(height/2), 0.5);
|
||||
glVertex3f(float(x - widtho)/(width/2), float(0 - heighto)/(height/2), 0.5);
|
||||
glVertex3f(float(x+4 - widtho)/(width/2), float(4 - heighto)/(height/2), 0.5);
|
||||
glVertex3f(float(x - widtho)/(width/2), float(4 - heighto)/(height/2), 0.5);
|
||||
glEnd();
|
||||
glReadPixels(x+2, 2+viewport_offset, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z);
|
||||
z -= 0.75f + 8e-6f;
|
||||
if (z<0.0f) z = -z;
|
||||
if (z > 0.01f) continue;
|
||||
if (z < bestz) {
|
||||
bestz = z;
|
||||
biasFactor = f;
|
||||
}
|
||||
//printf("f %g z %g\n", f, z);
|
||||
}
|
||||
//printf(" --> bias factor %g\n", biasFactor);
|
||||
glPopAttrib();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDepthBiasLevel( FxI32 level )
|
||||
{
|
||||
LOG("grDepthBiasLevel(%d)\r\n", level);
|
||||
if (level)
|
||||
{
|
||||
#ifdef ANDROID_EDITION
|
||||
glPolygonOffset(polygonOffsetFactor, polygonOffsetUnits);
|
||||
#else
|
||||
if(w_buffer_mode)
|
||||
glPolygonOffset(1.0f, -(float)level*zscale/255.0f);
|
||||
else
|
||||
glPolygonOffset(0, (float)level*biasFactor);
|
||||
#endif
|
||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
}
|
||||
else
|
||||
{
|
||||
glPolygonOffset(0,0);
|
||||
glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
}
|
||||
}
|
||||
|
||||
// draw
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDrawTriangle( const void *a, const void *b, const void *c )
|
||||
{
|
||||
LOG("grDrawTriangle()\r\n\t");
|
||||
|
||||
if(nvidia_viewport_hack && !render_to_texture)
|
||||
{
|
||||
glViewport(0, viewport_offset, viewport_width, viewport_height);
|
||||
nvidia_viewport_hack = 0;
|
||||
}
|
||||
|
||||
reloadTexture();
|
||||
|
||||
if(need_to_compile) compile_shader();
|
||||
|
||||
if(vertex_buffer_count + 3 > VERTEX_BUFFER_SIZE)
|
||||
{
|
||||
vbo_draw();
|
||||
}
|
||||
vertex_draw_mode = GL_TRIANGLES;
|
||||
memcpy(&vertex_buffer[vertex_buffer_count],a,VERTEX_SIZE);
|
||||
memcpy(&vertex_buffer[vertex_buffer_count+1],b,VERTEX_SIZE);
|
||||
memcpy(&vertex_buffer[vertex_buffer_count+2],c,VERTEX_SIZE);
|
||||
vertex_buffer_count += 3;
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDrawPoint( const void *pt )
|
||||
{
|
||||
/*
|
||||
float *x = (float*)pt + xy_off/sizeof(float);
|
||||
float *y = (float*)pt + xy_off/sizeof(float) + 1;
|
||||
float *z = (float*)pt + z_off/sizeof(float);
|
||||
float *q = (float*)pt + q_off/sizeof(float);
|
||||
unsigned char *pargb = (unsigned char*)pt + pargb_off;
|
||||
float *s0 = (float*)pt + st0_off/sizeof(float);
|
||||
float *t0 = (float*)pt + st0_off/sizeof(float) + 1;
|
||||
float *s1 = (float*)pt + st1_off/sizeof(float);
|
||||
float *t1 = (float*)pt + st1_off/sizeof(float) + 1;
|
||||
float *fog = (float*)pt + fog_ext_off/sizeof(float);
|
||||
LOG("grDrawPoint()\r\n");
|
||||
|
||||
if(nvidia_viewport_hack && !render_to_texture)
|
||||
{
|
||||
glViewport(0, viewport_offset, viewport_width, viewport_height);
|
||||
nvidia_viewport_hack = 0;
|
||||
}
|
||||
|
||||
reloadTexture();
|
||||
|
||||
if(need_to_compile) compile_shader();
|
||||
|
||||
glBegin(GL_POINTS);
|
||||
|
||||
if (nbTextureUnits > 2)
|
||||
{
|
||||
if (st0_en)
|
||||
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, *s0 / *q / (float)tex1_width,
|
||||
ytex(0, *t0 / *q / (float)tex1_height));
|
||||
if (st1_en)
|
||||
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, *s1 / *q / (float)tex0_width,
|
||||
ytex(1, *t1 / *q / (float)tex0_height));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (st0_en)
|
||||
glTexCoord2f(*s0 / *q / (float)tex0_width,
|
||||
ytex(0, *t0 / *q / (float)tex0_height));
|
||||
}
|
||||
if (pargb_en)
|
||||
glColor4f(pargb[2]/255.0f, pargb[1]/255.0f, pargb[0]/255.0f, pargb[3]/255.0f);
|
||||
if (fog_enabled && fog_coord_support)
|
||||
{
|
||||
if(!fog_ext_en || fog_enabled != 2)
|
||||
glSecondaryColor3f((1.0f / *q) / 255.0f, 0.0f, 0.0f);
|
||||
else
|
||||
glSecondaryColor3f((1.0f / *fog) / 255.0f, 0.0f, 0.0f);
|
||||
}
|
||||
glVertex4f((*x - (float)widtho) / (float)(width/2) / *q,
|
||||
-(*y - (float)heighto) / (float)(height/2) / *q, ZCALC(*z ,*q), 1.0f / *q);
|
||||
|
||||
glEnd();
|
||||
*/
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDrawLine( const void *a, const void *b )
|
||||
{
|
||||
/*
|
||||
float *a_x = (float*)a + xy_off/sizeof(float);
|
||||
float *a_y = (float*)a + xy_off/sizeof(float) + 1;
|
||||
float *a_z = (float*)a + z_off/sizeof(float);
|
||||
float *a_q = (float*)a + q_off/sizeof(float);
|
||||
unsigned char *a_pargb = (unsigned char*)a + pargb_off;
|
||||
float *a_s0 = (float*)a + st0_off/sizeof(float);
|
||||
float *a_t0 = (float*)a + st0_off/sizeof(float) + 1;
|
||||
float *a_s1 = (float*)a + st1_off/sizeof(float);
|
||||
float *a_t1 = (float*)a + st1_off/sizeof(float) + 1;
|
||||
float *a_fog = (float*)a + fog_ext_off/sizeof(float);
|
||||
|
||||
float *b_x = (float*)b + xy_off/sizeof(float);
|
||||
float *b_y = (float*)b + xy_off/sizeof(float) + 1;
|
||||
float *b_z = (float*)b + z_off/sizeof(float);
|
||||
float *b_q = (float*)b + q_off/sizeof(float);
|
||||
unsigned char *b_pargb = (unsigned char*)b + pargb_off;
|
||||
float *b_s0 = (float*)b + st0_off/sizeof(float);
|
||||
float *b_t0 = (float*)b + st0_off/sizeof(float) + 1;
|
||||
float *b_s1 = (float*)b + st1_off/sizeof(float);
|
||||
float *b_t1 = (float*)b + st1_off/sizeof(float) + 1;
|
||||
float *b_fog = (float*)b + fog_ext_off/sizeof(float);
|
||||
LOG("grDrawLine()\r\n");
|
||||
|
||||
if(nvidia_viewport_hack && !render_to_texture)
|
||||
{
|
||||
glViewport(0, viewport_offset, viewport_width, viewport_height);
|
||||
nvidia_viewport_hack = 0;
|
||||
}
|
||||
|
||||
reloadTexture();
|
||||
|
||||
if(need_to_compile) compile_shader();
|
||||
|
||||
glBegin(GL_LINES);
|
||||
|
||||
if (nbTextureUnits > 2)
|
||||
{
|
||||
if (st0_en)
|
||||
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, *a_s0 / *a_q / (float)tex1_width, ytex(0, *a_t0 / *a_q / (float)tex1_height));
|
||||
if (st1_en)
|
||||
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, *a_s1 / *a_q / (float)tex0_width, ytex(1, *a_t1 / *a_q / (float)tex0_height));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (st0_en)
|
||||
glTexCoord2f(*a_s0 / *a_q / (float)tex0_width, ytex(0, *a_t0 / *a_q / (float)tex0_height));
|
||||
}
|
||||
if (pargb_en)
|
||||
glColor4f(a_pargb[2]/255.0f, a_pargb[1]/255.0f, a_pargb[0]/255.0f, a_pargb[3]/255.0f);
|
||||
if (fog_enabled && fog_coord_support)
|
||||
{
|
||||
if(!fog_ext_en || fog_enabled != 2)
|
||||
glSecondaryColor3f((1.0f / *a_q) / 255.0f, 0.0f, 0.0f);
|
||||
else
|
||||
glSecondaryColor3f((1.0f / *a_fog) / 255.0f, 0.0f, 0.0f);
|
||||
}
|
||||
glVertex4f((*a_x - (float)widtho) / (float)(width/2) / *a_q,
|
||||
-(*a_y - (float)heighto) / (float)(height/2) / *a_q, ZCALC(*a_z, *a_q), 1.0f / *a_q);
|
||||
|
||||
if (nbTextureUnits > 2)
|
||||
{
|
||||
if (st0_en)
|
||||
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, *b_s0 / *b_q / (float)tex1_width,
|
||||
ytex(0, *b_t0 / *b_q / (float)tex1_height));
|
||||
if (st1_en)
|
||||
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, *b_s1 / *b_q / (float)tex0_width,
|
||||
ytex(1, *b_t1 / *b_q / (float)tex0_height));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (st0_en)
|
||||
glTexCoord2f(*b_s0 / *b_q / (float)tex0_width,
|
||||
ytex(0, *b_t0 / *b_q / (float)tex0_height));
|
||||
}
|
||||
if (pargb_en)
|
||||
glColor4f(b_pargb[2]/255.0f, b_pargb[1]/255.0f, b_pargb[0]/255.0f, b_pargb[3]/255.0f);
|
||||
if (fog_enabled && fog_coord_support)
|
||||
{
|
||||
if(!fog_ext_en || fog_enabled != 2)
|
||||
glSecondaryColor3f((1.0f / *b_q) / 255.0f, 0.0f, 0.0f);
|
||||
else
|
||||
glSecondaryColor3f((1.0f / *b_fog) / 255.0f, 0.0f, 0.0f);
|
||||
}
|
||||
glVertex4f((*b_x - (float)widtho) / (float)(width/2) / *b_q,
|
||||
-(*b_y - (float)heighto) / (float)(height/2) / *b_q, ZCALC(*b_z, *b_q), 1.0f / *b_q);
|
||||
|
||||
glEnd();
|
||||
*/
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDrawVertexArray(FxU32 mode, FxU32 Count, void *pointers2)
|
||||
{
|
||||
void **pointers = (void**)pointers2;
|
||||
LOG("grDrawVertexArray(%d,%d)\r\n", mode, Count);
|
||||
|
||||
if(nvidia_viewport_hack && !render_to_texture)
|
||||
{
|
||||
glViewport(0, viewport_offset, viewport_width, viewport_height);
|
||||
nvidia_viewport_hack = 0;
|
||||
}
|
||||
|
||||
reloadTexture();
|
||||
|
||||
if(need_to_compile) compile_shader();
|
||||
|
||||
if(mode != GR_TRIANGLE_FAN)
|
||||
{
|
||||
display_warning("grDrawVertexArray : unknown mode : %x", mode);
|
||||
}
|
||||
|
||||
vbo_enable();
|
||||
vbo_buffer(GL_TRIANGLE_FAN,0,Count,pointers[0]);
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grDrawVertexArrayContiguous(FxU32 mode, FxU32 Count, void *pointers, FxU32 stride)
|
||||
{
|
||||
LOG("grDrawVertexArrayContiguous(%d,%d,%d)\r\n", mode, Count, stride);
|
||||
|
||||
if(nvidia_viewport_hack && !render_to_texture)
|
||||
{
|
||||
glViewport(0, viewport_offset, viewport_width, viewport_height);
|
||||
nvidia_viewport_hack = 0;
|
||||
}
|
||||
|
||||
if(stride != 156)
|
||||
{
|
||||
LOGINFO("Incompatible stride\n");
|
||||
}
|
||||
|
||||
reloadTexture();
|
||||
|
||||
if(need_to_compile) compile_shader();
|
||||
|
||||
vbo_enable();
|
||||
|
||||
switch(mode)
|
||||
{
|
||||
case GR_TRIANGLE_STRIP:
|
||||
vbo_buffer(GL_TRIANGLE_STRIP,0,Count,pointers);
|
||||
break;
|
||||
case GR_TRIANGLE_FAN:
|
||||
vbo_buffer(GL_TRIANGLE_FAN,0,Count,pointers);
|
||||
break;
|
||||
default:
|
||||
display_warning("grDrawVertexArrayContiguous : unknown mode : %x", mode);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,900 @@
|
|||
/*
|
||||
* Glide64 - Glide video plugin for Nintendo 64 emulators.
|
||||
* Copyright (c) 2002 Dave2001
|
||||
* Copyright (c) 2003-2009 Sergey 'Gonetz' Lipski
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else // _WIN32
|
||||
#include <stdlib.h>
|
||||
#endif // _WIN32
|
||||
#include "glide.h"
|
||||
#include "main.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/* Napalm extensions to GrTextureFormat_t */
|
||||
#define GR_TEXFMT_ARGB_CMP_FXT1 0x11
|
||||
#define GR_TEXFMT_ARGB_8888 0x12
|
||||
#define GR_TEXFMT_YUYV_422 0x13
|
||||
#define GR_TEXFMT_UYVY_422 0x14
|
||||
#define GR_TEXFMT_AYUV_444 0x15
|
||||
#define GR_TEXFMT_ARGB_CMP_DXT1 0x16
|
||||
#define GR_TEXFMT_ARGB_CMP_DXT2 0x17
|
||||
#define GR_TEXFMT_ARGB_CMP_DXT3 0x18
|
||||
#define GR_TEXFMT_ARGB_CMP_DXT4 0x19
|
||||
#define GR_TEXFMT_ARGB_CMP_DXT5 0x1A
|
||||
#define GR_TEXTFMT_RGB_888 0xFF
|
||||
|
||||
int TMU_SIZE = 8*2048*2048;
|
||||
static unsigned char* texture = NULL;
|
||||
|
||||
int packed_pixels_support = -1;
|
||||
int ati_sucks = -1;
|
||||
float largest_supported_anisotropy = 1.0f;
|
||||
|
||||
#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
|
||||
#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
|
||||
#endif
|
||||
|
||||
int tex0_width, tex0_height, tex1_width, tex1_height;
|
||||
float lambda;
|
||||
|
||||
static int min_filter0, mag_filter0, wrap_s0, wrap_t0;
|
||||
static int min_filter1, mag_filter1, wrap_s1, wrap_t1;
|
||||
|
||||
unsigned char *filter(unsigned char *source, int width, int height, int *width2, int *height2);
|
||||
|
||||
typedef struct _texlist
|
||||
{
|
||||
unsigned int id;
|
||||
struct _texlist *next;
|
||||
} texlist;
|
||||
|
||||
static int nbTex = 0;
|
||||
static texlist *list = NULL;
|
||||
|
||||
#ifdef _WIN32
|
||||
extern PFNGLDELETERENDERBUFFERSEXTPROC glDeleteRenderbuffersEXT;
|
||||
extern PFNGLDELETEFRAMEBUFFERSEXTPROC glDeleteFramebuffersEXT;
|
||||
extern PFNGLCOMPRESSEDTEXIMAGE2DARBPROC glCompressedTexImage2DARB;
|
||||
#endif
|
||||
void remove_tex(unsigned int idmin, unsigned int idmax)
|
||||
{
|
||||
unsigned int *t;
|
||||
int n = 0;
|
||||
texlist *aux = list;
|
||||
int sz = nbTex;
|
||||
if (aux == NULL) return;
|
||||
t = (unsigned int*)malloc(sz * sizeof(int));
|
||||
while (aux && aux->id >= idmin && aux->id < idmax)
|
||||
{
|
||||
if (n >= sz)
|
||||
t = (unsigned int *) realloc(t, ++sz*sizeof(int));
|
||||
t[n++] = aux->id;
|
||||
aux = aux->next;
|
||||
free(list);
|
||||
list = aux;
|
||||
nbTex--;
|
||||
}
|
||||
while (aux != NULL && aux->next != NULL)
|
||||
{
|
||||
if (aux->next->id >= idmin && aux->next->id < idmax)
|
||||
{
|
||||
texlist *aux2 = aux->next->next;
|
||||
if (n >= sz)
|
||||
t = (unsigned int *) realloc(t, ++sz*sizeof(int));
|
||||
t[n++] = aux->next->id;
|
||||
free(aux->next);
|
||||
aux->next = aux2;
|
||||
nbTex--;
|
||||
}
|
||||
aux = aux->next;
|
||||
}
|
||||
glDeleteTextures(n, t);
|
||||
free(t);
|
||||
//printf("RMVTEX nbtex is now %d (%06x - %06x)\n", nbTex, idmin, idmax);
|
||||
}
|
||||
|
||||
|
||||
void add_tex(unsigned int id)
|
||||
{
|
||||
texlist *aux = list;
|
||||
texlist *aux2;
|
||||
//printf("ADDTEX nbtex is now %d (%06x)\n", nbTex, id);
|
||||
if (list == NULL || id < list->id)
|
||||
{
|
||||
nbTex++;
|
||||
list = (texlist*)malloc(sizeof(texlist));
|
||||
list->next = aux;
|
||||
list->id = id;
|
||||
return;
|
||||
}
|
||||
while (aux->next != NULL && aux->next->id < id) aux = aux->next;
|
||||
// ZIGGY added this test so that add_tex now accept re-adding an existing texture
|
||||
if (aux->next != NULL && aux->next->id == id) return;
|
||||
nbTex++;
|
||||
aux2 = aux->next;
|
||||
aux->next = (texlist*)malloc(sizeof(texlist));
|
||||
aux->next->id = id;
|
||||
aux->next->next = aux2;
|
||||
}
|
||||
|
||||
void init_textures()
|
||||
{
|
||||
tex0_width = tex0_height = tex1_width = tex1_height = 2;
|
||||
// ZIGGY because remove_tex isn't called (Pj64 doesn't like it), it's better
|
||||
// to leave these so that they'll be reused (otherwise we have a memory leak)
|
||||
// list = NULL;
|
||||
// nbTex = 0;
|
||||
|
||||
if (!texture) texture = (unsigned char*)malloc(2048*2048*4);
|
||||
}
|
||||
|
||||
void free_textures()
|
||||
{
|
||||
#ifndef WIN32
|
||||
// ZIGGY for some reasons, Pj64 doesn't like remove_tex on exit
|
||||
remove_tex(0x00000000, 0xFFFFFFFF);
|
||||
#endif
|
||||
if (texture != NULL) {
|
||||
free(texture);
|
||||
texture = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
FX_ENTRY FxU32 FX_CALL
|
||||
grTexMinAddress( GrChipID_t tmu )
|
||||
{
|
||||
LOG("grTexMinAddress(%d)\r\n", tmu);
|
||||
if (UMAmode)
|
||||
return 0;
|
||||
else
|
||||
return tmu*TMU_SIZE;
|
||||
}
|
||||
|
||||
FX_ENTRY FxU32 FX_CALL
|
||||
grTexMaxAddress( GrChipID_t tmu )
|
||||
{
|
||||
LOG("grTexMaxAddress(%d)\r\n", tmu);
|
||||
if (UMAmode)
|
||||
return TMU_SIZE*2 - 1;
|
||||
else
|
||||
return tmu*TMU_SIZE + TMU_SIZE - 1;
|
||||
}
|
||||
|
||||
FX_ENTRY FxU32 FX_CALL
|
||||
grTexTextureMemRequired( FxU32 evenOdd,
|
||||
GrTexInfo *info )
|
||||
{
|
||||
int width, height;
|
||||
LOG("grTextureMemRequired(%d)\r\n", evenOdd);
|
||||
if (info->largeLodLog2 != info->smallLodLog2) display_warning("grTexTextureMemRequired : loading more than one LOD");
|
||||
|
||||
if (info->aspectRatioLog2 < 0)
|
||||
{
|
||||
height = 1 << info->largeLodLog2;
|
||||
width = height >> -info->aspectRatioLog2;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = 1 << info->largeLodLog2;
|
||||
height = width >> info->aspectRatioLog2;
|
||||
}
|
||||
|
||||
switch(info->format)
|
||||
{
|
||||
case GR_TEXFMT_ALPHA_8:
|
||||
case GR_TEXFMT_INTENSITY_8: // I8 support - H.Morii
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_44:
|
||||
return width*height;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_1555:
|
||||
case GR_TEXFMT_ARGB_4444:
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_88:
|
||||
case GR_TEXFMT_RGB_565:
|
||||
return (width*height)<<1;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_8888:
|
||||
return (width*height)<<2;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT1: // FXT1,DXT1,5 support - H.Morii
|
||||
return ((((width+0x3)&~0x3)*((height+0x3)&~0x3))>>1);
|
||||
case GR_TEXFMT_ARGB_CMP_DXT3:
|
||||
return ((width+0x3)&~0x3)*((height+0x3)&~0x3);
|
||||
case GR_TEXFMT_ARGB_CMP_DXT5:
|
||||
return ((width+0x3)&~0x3)*((height+0x3)&~0x3);
|
||||
case GR_TEXFMT_ARGB_CMP_FXT1:
|
||||
return ((((width+0x7)&~0x7)*((height+0x3)&~0x3))>>1);
|
||||
default:
|
||||
display_warning("grTexTextureMemRequired : unknown texture format: %x", info->format);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
FX_ENTRY FxU32 FX_CALL
|
||||
grTexCalcMemRequired(
|
||||
GrLOD_t lodmin, GrLOD_t lodmax,
|
||||
GrAspectRatio_t aspect, GrTextureFormat_t fmt)
|
||||
{
|
||||
int width, height;
|
||||
LOG("grTexCalcMemRequired(%d, %d, %d, %d)\r\n", lodmin, lodmax, aspect, fmt);
|
||||
if (lodmax != lodmin) display_warning("grTexCalcMemRequired : loading more than one LOD");
|
||||
|
||||
if (aspect < 0)
|
||||
{
|
||||
height = 1 << lodmax;
|
||||
width = height >> -aspect;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = 1 << lodmax;
|
||||
height = width >> aspect;
|
||||
}
|
||||
|
||||
switch(fmt)
|
||||
{
|
||||
case GR_TEXFMT_ALPHA_8:
|
||||
case GR_TEXFMT_INTENSITY_8: // I8 support - H.Morii
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_44:
|
||||
return width*height;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_1555:
|
||||
case GR_TEXFMT_ARGB_4444:
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_88:
|
||||
case GR_TEXFMT_RGB_565:
|
||||
return (width*height)<<1;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_8888:
|
||||
return (width*height)<<2;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT1: // FXT1,DXT1,5 support - H.Morii
|
||||
return ((((width+0x3)&~0x3)*((height+0x3)&~0x3))>>1);
|
||||
case GR_TEXFMT_ARGB_CMP_DXT3:
|
||||
return ((width+0x3)&~0x3)*((height+0x3)&~0x3);
|
||||
case GR_TEXFMT_ARGB_CMP_DXT5:
|
||||
return ((width+0x3)&~0x3)*((height+0x3)&~0x3);
|
||||
case GR_TEXFMT_ARGB_CMP_FXT1:
|
||||
return ((((width+0x7)&~0x7)*((height+0x3)&~0x3))>>1);
|
||||
default:
|
||||
display_warning("grTexTextureMemRequired : unknown texture format: %x", fmt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int grTexFormatSize(int fmt)
|
||||
{
|
||||
int factor = -1;
|
||||
switch(fmt) {
|
||||
case GR_TEXFMT_ALPHA_8:
|
||||
case GR_TEXFMT_INTENSITY_8: // I8 support - H.Morii
|
||||
factor = 1;
|
||||
break;
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_44:
|
||||
factor = 1;
|
||||
break;
|
||||
case GR_TEXFMT_RGB_565:
|
||||
factor = 2;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_1555:
|
||||
factor = 2;
|
||||
break;
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_88:
|
||||
factor = 2;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_4444:
|
||||
factor = 2;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_8888:
|
||||
factor = 4;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT1: // FXT1,DXT1,5 support - H.Morii
|
||||
factor = 8; // HACKALERT: factor holds block bytes
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT3: // FXT1,DXT1,5 support - H.Morii
|
||||
factor = 16; // HACKALERT: factor holds block bytes
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT5:
|
||||
factor = 16;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_FXT1:
|
||||
factor = 8;
|
||||
break;
|
||||
default:
|
||||
display_warning("grTexFormatSize : unknown texture format: %x", fmt);
|
||||
}
|
||||
return factor;
|
||||
}
|
||||
|
||||
int grTexFormat2GLPackedFmt(int fmt, int * gltexfmt, int * glpixfmt, int * glpackfmt)
|
||||
{
|
||||
*gltexfmt = GL_RGBA;
|
||||
*glpixfmt = GL_RGBA;
|
||||
*glpackfmt = GL_UNSIGNED_BYTE;
|
||||
return 0;
|
||||
/*
|
||||
int factor = -1;
|
||||
switch(fmt) {
|
||||
case GR_TEXFMT_ALPHA_8:
|
||||
factor = 1;
|
||||
*gltexfmt = GL_INTENSITY8;
|
||||
*glpixfmt = GL_LUMINANCE;
|
||||
*glpackfmt = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
case GR_TEXFMT_INTENSITY_8: // I8 support - H.Morii
|
||||
factor = 1;
|
||||
*gltexfmt = GL_LUMINANCE8;
|
||||
*glpixfmt = GL_LUMINANCE;
|
||||
*glpackfmt = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_44:
|
||||
break;
|
||||
case GR_TEXFMT_RGB_565:
|
||||
factor = 2;
|
||||
*gltexfmt = GL_RGB;
|
||||
*glpixfmt = GL_RGB;
|
||||
*glpackfmt = GL_UNSIGNED_SHORT_5_6_5;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_1555:
|
||||
if (ati_sucks > 0) return -1; // ATI sucks as usual (fixes slowdown on ATI)
|
||||
factor = 2;
|
||||
*gltexfmt = GL_RGB5_A1;
|
||||
*glpixfmt = GL_BGRA;
|
||||
*glpackfmt = GL_UNSIGNED_SHORT_1_5_5_5_REV;
|
||||
break;
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_88:
|
||||
factor = 2;
|
||||
*gltexfmt = GL_LUMINANCE8_ALPHA8;
|
||||
*glpixfmt = GL_LUMINANCE_ALPHA;
|
||||
*glpackfmt = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_4444:
|
||||
factor = 2;
|
||||
*gltexfmt = GL_RGBA4;
|
||||
*glpixfmt = GL_BGRA;
|
||||
*glpackfmt = GL_UNSIGNED_SHORT_4_4_4_4_REV;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_8888:
|
||||
factor = 4;
|
||||
*gltexfmt = GL_RGBA8;
|
||||
*glpixfmt = GL_BGRA;
|
||||
*glpackfmt = GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT1: // FXT1,DXT1,5 support - H.Morii
|
||||
// HACKALERT: 3Dfx Glide uses GR_TEXFMT_ARGB_CMP_DXT1 for both opaque DXT1 and DXT1 with 1bit alpha.
|
||||
// GlideHQ compiled with GLIDE64_DXTN option enabled, uses opaqe DXT1 only.
|
||||
factor = 8; // HACKALERT: factor holds block bytes
|
||||
*gltexfmt = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; // these variables aren't used
|
||||
*glpixfmt = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
||||
*glpackfmt = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT3:
|
||||
factor = 16;
|
||||
*gltexfmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
||||
*glpixfmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
||||
*glpackfmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT5:
|
||||
factor = 16;
|
||||
*gltexfmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
*glpixfmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
*glpackfmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_FXT1:
|
||||
factor = 8;
|
||||
*gltexfmt = GL_COMPRESSED_RGBA_FXT1_3DFX;
|
||||
*glpixfmt = GL_COMPRESSED_RGBA_FXT1_3DFX;
|
||||
*glpackfmt = GL_COMPRESSED_RGBA_FXT1_3DFX; // XXX: what should we do about GL_COMPRESSED_RGB_FXT1_3DFX?
|
||||
break;
|
||||
default:
|
||||
display_warning("grTexFormat2GLPackedFmt : unknown texture format: %x", fmt);
|
||||
}
|
||||
return factor;
|
||||
*/
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grTexDownloadMipMap( GrChipID_t tmu,
|
||||
FxU32 startAddress,
|
||||
FxU32 evenOdd,
|
||||
GrTexInfo *info )
|
||||
{
|
||||
int width, height, i, j;
|
||||
int factor;
|
||||
int glformat = 0;
|
||||
int gltexfmt, glpixfmt, glpackfmt;
|
||||
LOG("grTexDownloadMipMap(%d,%d,%d)\r\n", tmu, startAddress, evenOdd);
|
||||
if (info->largeLodLog2 != info->smallLodLog2) display_warning("grTexDownloadMipMap : loading more than one LOD");
|
||||
|
||||
if (info->aspectRatioLog2 < 0)
|
||||
{
|
||||
height = 1 << info->largeLodLog2;
|
||||
width = height >> -info->aspectRatioLog2;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = 1 << info->largeLodLog2;
|
||||
height = width >> info->aspectRatioLog2;
|
||||
}
|
||||
|
||||
if (!packed_pixels_support)
|
||||
factor = -1;
|
||||
else
|
||||
factor = grTexFormat2GLPackedFmt(info->format, &gltexfmt, &glpixfmt, &glpackfmt);
|
||||
|
||||
if (factor < 0) {
|
||||
|
||||
// VP fixed the texture conversions to be more accurate, also swapped
|
||||
// the for i/j loops so that is is less likely to break the memory cache
|
||||
register int n = 0, m = 0;
|
||||
switch(info->format)
|
||||
{
|
||||
case GR_TEXFMT_ALPHA_8:
|
||||
for (i=0; i<height; i++)
|
||||
{
|
||||
for (j=0; j<width; j++)
|
||||
{
|
||||
unsigned int texel = (unsigned int)((unsigned char*)info->data)[m];
|
||||
texel |= (texel << 8);
|
||||
texel |= (texel << 16);
|
||||
((unsigned int*)texture)[n] = texel;
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
factor = 1;
|
||||
glformat = GL_RGBA;
|
||||
break;
|
||||
case GR_TEXFMT_INTENSITY_8: // I8 support - H.Morii
|
||||
for (i=0; i<height; i++)
|
||||
{
|
||||
for (j=0; j<width; j++)
|
||||
{
|
||||
unsigned int texel = (unsigned int)((unsigned char*)info->data)[m];
|
||||
texel |= (0xFF000000 | (texel << 16) | (texel << 8));
|
||||
((unsigned int*)texture)[n] = texel;
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
factor = 1;
|
||||
glformat = GL_ALPHA;
|
||||
break;
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_44:
|
||||
#if 1
|
||||
for (i=0; i<height; i++)
|
||||
{
|
||||
for (j=0; j<width; j++)
|
||||
{
|
||||
unsigned int texel = (unsigned int)((unsigned char*)info->data)[m];
|
||||
#if 1
|
||||
/* accurate conversion */
|
||||
unsigned int texel_hi = (texel & 0x000000F0) << 20;
|
||||
unsigned int texel_low = texel & 0x0000000F;
|
||||
texel_low |= (texel_low << 4);
|
||||
texel_hi |= ((texel_hi << 4) | (texel_low << 16) | (texel_low << 8) | texel_low);
|
||||
#else
|
||||
unsigned int texel_hi = (texel & 0x000000F0) << 24;
|
||||
unsigned int texel_low = (texel & 0x0000000F) << 4;
|
||||
texel_hi |= ((texel_low << 16) | (texel_low << 8) | texel_low);
|
||||
#endif
|
||||
((unsigned int*)texture)[n] = texel_hi;
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
factor = 1;
|
||||
glformat = GL_LUMINANCE_ALPHA;
|
||||
#endif
|
||||
break;
|
||||
case GR_TEXFMT_RGB_565:
|
||||
for (i=0; i<height; i++)
|
||||
{
|
||||
for (j=0; j<width; j++)
|
||||
{
|
||||
unsigned int texel = (unsigned int)((unsigned short*)info->data)[m];
|
||||
unsigned int B = texel & 0x0000F800;
|
||||
unsigned int G = texel & 0x000007E0;
|
||||
unsigned int R = texel & 0x0000001F;
|
||||
#if 0
|
||||
/* accurate conversion */
|
||||
((unsigned int*)texture)[n] = 0xFF000000 | (R << 19) | ((R >> 2) << 16) | (G << 5) | ((G >> 9) << 8) | (B >> 8) | (B >> 13);
|
||||
#else
|
||||
((unsigned int*)texture)[n] = 0xFF000000 | (R << 19) | (G << 5) | (B >> 8);
|
||||
#endif
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
factor = 2;
|
||||
glformat = GL_RGB;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_1555:
|
||||
for (i=0; i<height; i++)
|
||||
{
|
||||
for (j=0; j<width; j++)
|
||||
{
|
||||
unsigned int texel = (unsigned int)((unsigned short*)info->data)[m];
|
||||
unsigned int A = texel & 0x00008000 ? 0xFF000000 : 0;
|
||||
unsigned int B = texel & 0x00007C00;
|
||||
unsigned int G = texel & 0x000003E0;
|
||||
unsigned int R = texel & 0x0000001F;
|
||||
#if 0
|
||||
/* accurate conversion */
|
||||
((unsigned int*)texture)[n] = A | (R << 19) | ((R >> 2) << 16) | (G << 6) | ((G >> 8) << 8) | (B >> 7) | (B >> 12);
|
||||
#else
|
||||
((unsigned int*)texture)[n] = A | (R << 19) | (G << 6) | (B >> 7);
|
||||
#endif
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
factor = 2;
|
||||
glformat = GL_RGBA;
|
||||
break;
|
||||
case GR_TEXFMT_ALPHA_INTENSITY_88:
|
||||
for (i=0; i<height; i++)
|
||||
{
|
||||
for (j=0; j<width; j++)
|
||||
{
|
||||
unsigned int AI = (unsigned int)((unsigned short*)info->data)[m];
|
||||
unsigned int I = (unsigned int)(AI & 0x000000FF);
|
||||
((unsigned int*)texture)[n] = (AI << 16) | (I << 8) | I;
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
factor = 2;
|
||||
glformat = GL_LUMINANCE_ALPHA;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_4444:
|
||||
|
||||
for (i=0; i<height; i++)
|
||||
{
|
||||
for (j=0; j<width; j++)
|
||||
{
|
||||
unsigned int texel = (unsigned int)((unsigned short*)info->data)[m];
|
||||
unsigned int A = texel & 0x0000F000;
|
||||
unsigned int B = texel & 0x00000F00;
|
||||
unsigned int G = texel & 0x000000F0;
|
||||
unsigned int R = texel & 0x0000000F;
|
||||
#if 0
|
||||
/* accurate conversion */
|
||||
((unsigned int*)texture)[n] = (A << 16) | (A << 12) | (R << 20) | (R << 16) | (G << 8) | (G << 4) | (B >> 4) | (B >> 8);
|
||||
#else
|
||||
((unsigned int*)texture)[n] = (A << 16) | (R << 20) | (G << 8) | (B >> 4);
|
||||
#endif
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
factor = 2;
|
||||
glformat = GL_RGBA;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_8888:
|
||||
for (i=0; i<height; i++)
|
||||
{
|
||||
for (j=0; j<width; j++)
|
||||
{
|
||||
unsigned int texel = ((unsigned int*)info->data)[m];
|
||||
unsigned int A = texel & 0xFF000000;
|
||||
unsigned int B = texel & 0x00FF0000;
|
||||
unsigned int G = texel & 0x0000FF00;
|
||||
unsigned int R = texel & 0x000000FF;
|
||||
((unsigned int*)texture)[n] = A | (R << 16) | G | (B >> 16);
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
factor = 4;
|
||||
glformat = GL_RGBA;
|
||||
break;
|
||||
/*
|
||||
case GR_TEXFMT_ARGB_CMP_DXT1: // FXT1,DXT1,5 support - H.Morii
|
||||
factor = 8; // HACKALERT: factor holds block bytes
|
||||
glformat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT3: // FXT1,DXT1,5 support - H.Morii
|
||||
factor = 16; // HACKALERT: factor holds block bytes
|
||||
glformat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_DXT5:
|
||||
factor = 16;
|
||||
glformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
break;
|
||||
case GR_TEXFMT_ARGB_CMP_FXT1:
|
||||
factor = 8;
|
||||
glformat = GL_COMPRESSED_RGBA_FXT1_3DFX;
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
display_warning("grTexDownloadMipMap : unknown texture format: %x", info->format);
|
||||
factor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (nbTextureUnits <= 2)
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
else
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
|
||||
switch(info->format)
|
||||
{
|
||||
case GR_TEXFMT_ARGB_CMP_DXT1:
|
||||
case GR_TEXFMT_ARGB_CMP_DXT3:
|
||||
case GR_TEXFMT_ARGB_CMP_DXT5:
|
||||
case GR_TEXFMT_ARGB_CMP_FXT1:
|
||||
remove_tex(startAddress+1, startAddress+1+((width*height*factor)>>4));
|
||||
break;
|
||||
default:
|
||||
remove_tex(startAddress+1, startAddress+1+(width*height*factor));
|
||||
}
|
||||
|
||||
add_tex(startAddress+1);
|
||||
glBindTexture(GL_TEXTURE_2D, startAddress+1);
|
||||
|
||||
if (largest_supported_anisotropy > 1.0f)
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, largest_supported_anisotropy);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
|
||||
/*
|
||||
switch(info->format)
|
||||
{
|
||||
case GR_TEXFMT_ARGB_CMP_DXT1:
|
||||
case GR_TEXFMT_ARGB_CMP_DXT3:
|
||||
case GR_TEXFMT_ARGB_CMP_DXT5:
|
||||
case GR_TEXFMT_ARGB_CMP_FXT1:
|
||||
glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, (glformat ? glformat : gltexfmt), width, height, 0, (width*height*factor)>>4, info->data);
|
||||
break;
|
||||
default:
|
||||
if (glformat) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, glformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
|
||||
} else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, gltexfmt, width, height, 0, glpixfmt, glpackfmt, info->data);
|
||||
}
|
||||
*/
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, default_texture);
|
||||
}
|
||||
|
||||
int CheckTextureBufferFormat(GrChipID_t tmu, FxU32 startAddress, GrTexInfo *info );
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grTexSource( GrChipID_t tmu,
|
||||
FxU32 startAddress,
|
||||
FxU32 evenOdd,
|
||||
GrTexInfo *info )
|
||||
{
|
||||
LOG("grTexSource(%d,%d,%d)\r\n", tmu, startAddress, evenOdd);
|
||||
|
||||
if (tmu == GR_TMU1 || nbTextureUnits <= 2)
|
||||
{
|
||||
if (tmu == GR_TMU1 && nbTextureUnits <= 2) return;
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
if (info->aspectRatioLog2 < 0)
|
||||
{
|
||||
tex0_height = 256;
|
||||
tex0_width = tex0_height >> -info->aspectRatioLog2;
|
||||
}
|
||||
else
|
||||
{
|
||||
tex0_width = 256;
|
||||
tex0_height = tex0_width >> info->aspectRatioLog2;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, startAddress+1);
|
||||
#ifdef VPDEBUG
|
||||
dump_tex(startAddress+1);
|
||||
#endif
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t0);
|
||||
}
|
||||
else
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
|
||||
if (info->aspectRatioLog2 < 0)
|
||||
{
|
||||
tex1_height = 256;
|
||||
tex1_width = tex1_height >> -info->aspectRatioLog2;
|
||||
}
|
||||
else
|
||||
{
|
||||
tex1_width = 256;
|
||||
tex1_height = tex1_width >> info->aspectRatioLog2;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, startAddress+1);
|
||||
#ifdef VPDEBUG
|
||||
dump_tex(startAddress+1);
|
||||
#endif
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t1);
|
||||
}
|
||||
if(!CheckTextureBufferFormat(tmu, startAddress+1, info))
|
||||
{
|
||||
if(tmu == 0 && blackandwhite1 != 0)
|
||||
{
|
||||
blackandwhite1 = 0;
|
||||
need_to_compile = 1;
|
||||
}
|
||||
if(tmu == 1 && blackandwhite0 != 0)
|
||||
{
|
||||
blackandwhite0 = 0;
|
||||
need_to_compile = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
extern int auxbuffer;
|
||||
static int oldbuffer;
|
||||
FX_ENTRY void FX_CALL grAuxBufferExt( GrBuffer_t buffer );
|
||||
if (auxbuffer == GR_BUFFER_AUXBUFFER && auxbuffer != oldbuffer)
|
||||
grAuxBufferExt(auxbuffer);
|
||||
oldbuffer = auxbuffer;
|
||||
#endif
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grTexDetailControl(
|
||||
GrChipID_t tmu,
|
||||
int lod_bias,
|
||||
FxU8 detail_scale,
|
||||
float detail_max
|
||||
)
|
||||
{
|
||||
LOG("grTexDetailControl(%d,%d,%d,%d)\r\n", tmu, lod_bias, detail_scale, detail_max);
|
||||
if (lod_bias != 31 && detail_scale != 7)
|
||||
{
|
||||
if (!lod_bias && !detail_scale && !detail_max) return;
|
||||
else
|
||||
display_warning("grTexDetailControl : %d, %d, %f", lod_bias, detail_scale, detail_max);
|
||||
}
|
||||
lambda = detail_max;
|
||||
if(lambda > 1.0f)
|
||||
{
|
||||
lambda = 1.0f - (255.0f - lambda);
|
||||
}
|
||||
if(lambda > 1.0f) display_warning("lambda:%f", lambda);
|
||||
|
||||
set_lambda();
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grTexLodBiasValue(GrChipID_t tmu, float bias )
|
||||
{
|
||||
LOG("grTexLodBiasValue(%d,%f)\r\n", tmu, bias);
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grTexFilterMode(
|
||||
GrChipID_t tmu,
|
||||
GrTextureFilterMode_t minfilter_mode,
|
||||
GrTextureFilterMode_t magfilter_mode
|
||||
)
|
||||
{
|
||||
LOG("grTexFilterMode(%d,%d,%d)\r\n", tmu, minfilter_mode, magfilter_mode);
|
||||
if (tmu == GR_TMU1 || nbTextureUnits <= 2)
|
||||
{
|
||||
if (tmu == GR_TMU1 && nbTextureUnits <= 2) return;
|
||||
if (minfilter_mode == GR_TEXTUREFILTER_POINT_SAMPLED) min_filter0 = GL_NEAREST;
|
||||
else min_filter0 = GL_LINEAR;
|
||||
|
||||
if (magfilter_mode == GR_TEXTUREFILTER_POINT_SAMPLED) mag_filter0 = GL_NEAREST;
|
||||
else mag_filter0 = GL_LINEAR;
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (minfilter_mode == GR_TEXTUREFILTER_POINT_SAMPLED) min_filter1 = GL_NEAREST;
|
||||
else min_filter1 = GL_LINEAR;
|
||||
|
||||
if (magfilter_mode == GR_TEXTUREFILTER_POINT_SAMPLED) mag_filter1 = GL_NEAREST;
|
||||
else mag_filter1 = GL_LINEAR;
|
||||
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter1);
|
||||
}
|
||||
}
|
||||
|
||||
FX_ENTRY void FX_CALL
|
||||
grTexClampMode(
|
||||
GrChipID_t tmu,
|
||||
GrTextureClampMode_t s_clampmode,
|
||||
GrTextureClampMode_t t_clampmode
|
||||
)
|
||||
{
|
||||
LOG("grTexClampMode(%d, %d, %d)\r\n", tmu, s_clampmode, t_clampmode);
|
||||
if (tmu == GR_TMU1 || nbTextureUnits <= 2)
|
||||
{
|
||||
if (tmu == GR_TMU1 && nbTextureUnits <= 2) return;
|
||||
switch(s_clampmode)
|
||||
{
|
||||
case GR_TEXTURECLAMP_WRAP:
|
||||
wrap_s0 = GL_REPEAT;
|
||||
break;
|
||||
case GR_TEXTURECLAMP_CLAMP:
|
||||
wrap_s0 = GL_CLAMP_TO_EDGE;
|
||||
break;
|
||||
case GR_TEXTURECLAMP_MIRROR_EXT:
|
||||
wrap_s0 = GL_MIRRORED_REPEAT;
|
||||
break;
|
||||
default:
|
||||
display_warning("grTexClampMode : unknown s_clampmode : %x", s_clampmode);
|
||||
}
|
||||
switch(t_clampmode)
|
||||
{
|
||||
case GR_TEXTURECLAMP_WRAP:
|
||||
wrap_t0 = GL_REPEAT;
|
||||
break;
|
||||
case GR_TEXTURECLAMP_CLAMP:
|
||||
wrap_t0 = GL_CLAMP_TO_EDGE;
|
||||
break;
|
||||
case GR_TEXTURECLAMP_MIRROR_EXT:
|
||||
wrap_t0 = GL_MIRRORED_REPEAT;
|
||||
break;
|
||||
default:
|
||||
display_warning("grTexClampMode : unknown t_clampmode : %x", t_clampmode);
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t0);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(s_clampmode)
|
||||
{
|
||||
case GR_TEXTURECLAMP_WRAP:
|
||||
wrap_s1 = GL_REPEAT;
|
||||
break;
|
||||
case GR_TEXTURECLAMP_CLAMP:
|
||||
wrap_s1 = GL_CLAMP_TO_EDGE;
|
||||
break;
|
||||
case GR_TEXTURECLAMP_MIRROR_EXT:
|
||||
wrap_s1 = GL_MIRRORED_REPEAT;
|
||||
break;
|
||||
default:
|
||||
display_warning("grTexClampMode : unknown s_clampmode : %x", s_clampmode);
|
||||
}
|
||||
switch(t_clampmode)
|
||||
{
|
||||
case GR_TEXTURECLAMP_WRAP:
|
||||
wrap_t1 = GL_REPEAT;
|
||||
break;
|
||||
case GR_TEXTURECLAMP_CLAMP:
|
||||
wrap_t1 = GL_CLAMP_TO_EDGE;
|
||||
break;
|
||||
case GR_TEXTURECLAMP_MIRROR_EXT:
|
||||
wrap_t1 = GL_MIRRORED_REPEAT;
|
||||
break;
|
||||
default:
|
||||
display_warning("grTexClampMode : unknown t_clampmode : %x", t_clampmode);
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t1);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue