split out shader generation from nv2a.c

This commit is contained in:
espes 2015-08-10 09:21:45 -07:00
parent 87060feaac
commit 27cf9cd4da
3 changed files with 641 additions and 551 deletions

View File

@ -2,6 +2,7 @@
* QEMU Geforce NV2A implementation
*
* Copyright (c) 2012 espes
* Copyright (c) 2015 JayFoxRox
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@ -32,8 +33,7 @@
#include "hw/xbox/g-lru-cache.h"
#include "hw/xbox/swizzle.h"
#include "hw/xbox/u_format_r11g11b10f.h"
#include "hw/xbox/nv2a_vsh.h"
#include "hw/xbox/nv2a_psh.h"
#include "hw/xbox/nv2a_shaders.h"
#include "hw/xbox/nv2a.h"
@ -1269,31 +1269,11 @@ static const SurfaceColorFormatInfo kelvin_surface_color_format_map[] = {
{4, GL_RGBA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV},
};
#define NV2A_VERTEX_ATTR_POSITION 0
#define NV2A_VERTEX_ATTR_WEIGHT 1
#define NV2A_VERTEX_ATTR_NORMAL 2
#define NV2A_VERTEX_ATTR_DIFFUSE 3
#define NV2A_VERTEX_ATTR_SPECULAR 4
#define NV2A_VERTEX_ATTR_FOG 5
#define NV2A_VERTEX_ATTR_POINT_SIZE 6
#define NV2A_VERTEX_ATTR_BACK_DIFFUSE 7
#define NV2A_VERTEX_ATTR_BACK_SPECULAR 8
#define NV2A_VERTEX_ATTR_TEXTURE0 9
#define NV2A_VERTEX_ATTR_TEXTURE1 10
#define NV2A_VERTEX_ATTR_TEXTURE2 11
#define NV2A_VERTEX_ATTR_TEXTURE3 12
#define NV2A_VERTEX_ATTR_RESERVED1 13
#define NV2A_VERTEX_ATTR_RESERVED2 14
#define NV2A_VERTEX_ATTR_RESERVED3 15
#define NV2A_CRYSTAL_FREQ 13500000
#define NV2A_NUM_CHANNELS 32
#define NV2A_NUM_SUBCHANNELS 8
#define NV2A_MAX_BATCH_LENGTH 0xFFFF
#define NV2A_MAX_TRANSFORM_PROGRAM_LENGTH 136
#define NV2A_VERTEXSHADER_CONSTANTS 192
#define NV2A_VERTEXSHADER_ATTRIBUTES 16
#define NV2A_MAX_TEXTURES 4
@ -1379,48 +1359,6 @@ typedef struct VertexShaderConstant {
uint32_t data[4];
} VertexShaderConstant;
typedef struct ShaderState {
/* fragment shader - register combiner stuff */
uint32_t combiner_control;
uint32_t shader_stage_program;
uint32_t other_stage_input;
uint32_t final_inputs_0;
uint32_t final_inputs_1;
uint32_t rgb_inputs[8], rgb_outputs[8];
uint32_t alpha_inputs[8], alpha_outputs[8];
bool rect_tex[4];
bool compare_mode[4][4];
bool alphakill[4];
bool alpha_test;
enum PshAlphaFunc alpha_func;
bool texture_matrix_enable[4];
enum VshTexgen texgen[4][4];
enum VshSkinning skinning;
bool normalization;
bool fixed_function;
/* vertex program */
bool vertex_program;
uint32_t program_data[NV2A_MAX_TRANSFORM_PROGRAM_LENGTH][VSH_TOKEN_SIZE];
int program_length;
/* primitive format for geomotry shader */
unsigned int primitive_mode;
} ShaderState;
typedef struct ShaderBinding {
GLuint gl_program;
GLint psh_constant_loc[9][2];
GLint vsh_constant_loc[NV2A_VERTEXSHADER_CONSTANTS];
} ShaderBinding;
typedef struct Surface {
bool draw_dirty;
bool buffer_dirty;
@ -2708,492 +2646,6 @@ static void pgraph_bind_textures(NV2AState *d)
NV2A_GL_DGROUP_END();
}
/* hash and equality for shader cache hash table */
static guint shader_hash(gconstpointer key)
{
return fnv_hash(key, sizeof(ShaderState));
}
static gboolean shader_equal(gconstpointer a, gconstpointer b)
{
const ShaderState *as = a, *bs = b;
return memcmp(as, bs, sizeof(ShaderState)) == 0;
}
static void generate_geometry_shader_pass_vertex(QString* s, const char* v)
{
qstring_append_fmt(s, " gD0 = vD0[%s];\n", v);
qstring_append_fmt(s, " gD1 = vD1[%s];\n", v);
qstring_append_fmt(s, " gB0 = vB0[%s];\n", v);
qstring_append_fmt(s, " gB1 = vB1[%s];\n", v);
qstring_append_fmt(s, " gFog = vFog[%s];\n", v);
qstring_append_fmt(s, " gT0 = vT0[%s];\n", v);
qstring_append_fmt(s, " gT1 = vT1[%s];\n", v);
qstring_append_fmt(s, " gT2 = vT2[%s];\n", v);
qstring_append_fmt(s, " gT3 = vT3[%s];\n", v);
qstring_append_fmt(s, " gPos_w = vPos_w[%s];\n", v);
qstring_append_fmt(s, " gl_Position = gl_in[%s].gl_Position;\n", v);
qstring_append(s, " EmitVertex();\n");
}
static QString* generate_geometry_shader(unsigned int primitive_mode)
{
/* generate a geometry shader to support deprecated primitive types */
QString* s = qstring_new();
qstring_append(s, "#version 330\n");
qstring_append(s, "\n");
switch (primitive_mode) {
case NV097_SET_BEGIN_END_OP_QUADS:
qstring_append(s, "layout(lines_adjacency) in;\n");
qstring_append(s, "layout(triangle_strip, max_vertices = 4) out;\n");
break;
default:
assert(false);
break;
}
qstring_append(s, "\n");
qstring_append(s, "noperspective in float vPos_w[];\n");
qstring_append(s, "noperspective in vec4 vD0[];\n");
qstring_append(s, "noperspective in vec4 vD1[];\n");
qstring_append(s, "noperspective in vec4 vB0[];\n");
qstring_append(s, "noperspective in vec4 vB1[];\n");
qstring_append(s, "noperspective in vec4 vFog[];\n");
qstring_append(s, "noperspective in vec4 vT0[];\n");
qstring_append(s, "noperspective in vec4 vT1[];\n");
qstring_append(s, "noperspective in vec4 vT2[];\n");
qstring_append(s, "noperspective in vec4 vT3[];\n");
qstring_append(s, "\n");
qstring_append(s, "noperspective out float gPos_w;\n");
qstring_append(s, "noperspective out vec4 gD0;\n");
qstring_append(s, "noperspective out vec4 gD1;\n");
qstring_append(s, "noperspective out vec4 gB0;\n");
qstring_append(s, "noperspective out vec4 gB1;\n");
qstring_append(s, "noperspective out vec4 gFog;\n");
qstring_append(s, "noperspective out vec4 gT0;\n");
qstring_append(s, "noperspective out vec4 gT1;\n");
qstring_append(s, "noperspective out vec4 gT2;\n");
qstring_append(s, "noperspective out vec4 gT3;\n");
qstring_append(s, "\n");
qstring_append(s, "void main() {\n");
switch (primitive_mode) {
case NV097_SET_BEGIN_END_OP_QUADS:
generate_geometry_shader_pass_vertex(s, "0");
generate_geometry_shader_pass_vertex(s, "1");
generate_geometry_shader_pass_vertex(s, "3");
generate_geometry_shader_pass_vertex(s, "2");
qstring_append(s, "EndPrimitive();\n");
break;
default:
assert(false);
break;
}
qstring_append(s, "}\n");
return s;
}
static ShaderBinding* generate_shaders(const ShaderState state)
{
int i, j;
GLint compiled = 0;
bool with_geom = state.primitive_mode == NV097_SET_BEGIN_END_OP_QUADS;
char v_prefix = with_geom ? 'v' : 'g';
GLuint program = glCreateProgram();
/* create the vertex shader */
QString *vertex_shader_code = NULL;
if (state.fixed_function) {
/* generate vertex shader mimicking fixed function */
vertex_shader_code = qstring_new();
qstring_append(vertex_shader_code,
"#version 330\n"
"\n"
"in vec4 position;\n"
"in vec4 weight;\n"
"in vec3 normal;\n"
"in vec4 diffuse;\n"
"in vec4 specular;\n"
"in float fogCoord;\n"
"in vec4 backDiffuse;\n"
"in vec4 backSpecular;\n"
"in vec4 texture0;\n"
"in vec4 texture1;\n"
"in vec4 texture2;\n"
"in vec4 texture3;\n"
"\n");
qstring_append_fmt(vertex_shader_code,
"noperspective out float %cPos_w;\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cD0 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cD1 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cB0 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cB1 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cFog = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cT0 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cT1 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cT2 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cT3 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append(vertex_shader_code,
"\n"
/* FIXME: Add these uniforms using code when they are used */
"uniform mat4 texMat0;\n"
"uniform mat4 texMat1;\n"
"uniform mat4 texMat2;\n"
"uniform mat4 texMat3;\n"
"uniform mat4 modelViewMat0;\n"
"uniform mat4 modelViewMat1;\n"
"uniform mat4 modelViewMat2;\n"
"uniform mat4 modelViewMat3;\n"
"uniform mat4 invModelViewMat0;\n"
"uniform mat4 invModelViewMat1;\n"
"uniform mat4 invModelViewMat2;\n"
"uniform mat4 invModelViewMat3;\n"
"uniform mat4 projectionMat; /* FIXME: when is this used? */\n"
"uniform mat4 compositeMat;\n"
"uniform mat4 invViewport;\n"
"\n"
"void main() {\n");
/* Skinning */
unsigned int count;
bool mix;
switch (state.skinning) {
case SKINNING_OFF:
count = 0; break;
case SKINNING_1WEIGHTS:
mix = true; count = 2; break;
case SKINNING_2WEIGHTS:
mix = true; count = 3; break;
case SKINNING_3WEIGHTS:
mix = true; count = 4; break;
case SKINNING_2WEIGHTS2MATRICES:
mix = false; count = 2; break;
case SKINNING_3WEIGHTS3MATRICES:
mix = false; count = 3; break;
case SKINNING_4WEIGHTS4MATRICES:
mix = false; count = 4; break;
default:
assert(false);
break;
}
qstring_append_fmt(vertex_shader_code, "/* Skinning mode %d */\n",
state.skinning);
if (count == 0) {
qstring_append(vertex_shader_code, "vec4 tPosition = position * modelViewMat0;\n");
/* FIXME: Is the normal still transformed? */
qstring_append(vertex_shader_code, "vec3 tNormal = (vec4(normal, 0.0) * invModelViewMat0).xyz;\n");
} else {
qstring_append(vertex_shader_code, "vec4 tPosition = vec4(0.0);\n");
qstring_append(vertex_shader_code, "vec3 tNormal = vec3(0.0);\n");
if (mix) {
/* Tweening */
if (count == 2) {
qstring_append(vertex_shader_code,
"tPosition += mix(position * modelViewMat1,\n"
" position * modelViewMat0, weight.x);\n"
"tNormal += mix((vec4(normal, 0.0) * invModelViewMat1).xyz,\n"
" (vec4(normal, 0.0) * invModelViewMat0).xyz, weight.x);\n");
} else {
/* FIXME: Not sure how blend weights are calculated */
assert(false);
}
} else {
/* Individual matrices */
for (i = 0; i < count; i++) {
char c = "xyzw"[i];
qstring_append_fmt(vertex_shader_code, "tPosition += position * modelViewMat%d * weight.%c;\n",
i, c);
qstring_append_fmt(vertex_shader_code, "tNormal += (vec4(normal, 0.0) * invModelViewMat%d * weight.%c).xyz;\n",
i, c);
}
assert(false); /* FIXME: Untested */
}
}
/* Normalization */
if (state.normalization) {
qstring_append(vertex_shader_code, "tNormal = normalize(tNormal);\n");
}
/* Texgen */
for (i = 0; i < 4; i++) {
qstring_append_fmt(vertex_shader_code, "/* Texgen for stage %d */\n",
i);
qstring_append_fmt(vertex_shader_code, "vec4 tTexture%d;\n",
i);
/* Set each component individually */
/* FIXME: could be nicer if some channels share the same texgen */
for (j = 0; j < 4; j++) {
char c = "xyzw"[j];
switch (state.texgen[i][j]) {
case TEXGEN_DISABLE:
qstring_append_fmt(vertex_shader_code, "tTexture%d.%c = texture%d.%c;\n",
i, c, i, c);
break;
case TEXGEN_EYE_LINEAR:
qstring_append_fmt(vertex_shader_code, "tTexture%d.%c = tPosition.%c;\n",
i, c, c);
break;
case TEXGEN_OBJECT_LINEAR:
qstring_append_fmt(vertex_shader_code, "tTexture%d.%c = position.%c;\n",
i, c, c);
break;
case TEXGEN_SPHERE_MAP:
assert(i < 2); /* Channels S,T only! */
assert(false);
break;
case TEXGEN_REFLECTION_MAP:
assert(i < 3); /* Channels S,T,R only! */
qstring_append_fmt(vertex_shader_code, "tTexture%d.%c = reflect(???, tNormal).%c;\n",
i, c, c);
assert(false); /* FIXME: Code not complete yet! */
break;
case TEXGEN_NORMAL_MAP:
assert(i < 3); /* Channels S,T,R only! */
qstring_append_fmt(vertex_shader_code, "tTexture%d.%c = tNormal.%c;\n",
i, c, c);
break;
default:
assert(false);
break;
}
}
}
/* Apply texture matrices */
for (i = 0; i < 4; i++) {
if (state.texture_matrix_enable[i]) {
qstring_append_fmt(vertex_shader_code, "tTexture%d = tTexture%d * texMat%d;\n",
i, i, i);
}
}
/* If skinning is off the composite matrix already includes the MV matrix */
if (state.skinning == SKINNING_OFF) {
qstring_append(vertex_shader_code, "tPosition = position;\n");
}
qstring_append(vertex_shader_code,
" gl_Position = invViewport * (tPosition * compositeMat);\n"
/* temp hack: the composite matrix includes the view transform... */
//" gl_Position = position * compositeMat;\n"
//" gl_Position.x = (gl_Position.x - 320.0) / 320.0;\n"
//" gl_Position.y = -(gl_Position.y - 240.0) / 240.0;\n"
" gl_Position.z = gl_Position.z * 2.0 - gl_Position.w;\n");
qstring_append_fmt(vertex_shader_code,
"%cPos_w = 1.0/gl_Position.w;\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cD0 = diffuse * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cD1 = specular * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cB0 = backDiffuse * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cB1 = backSpecular * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cT0 = tTexture0 * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cT1 = tTexture1 * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cT2 = tTexture2 * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cT3 = tTexture3 * %cPos_w;\n", v_prefix, v_prefix);
qstring_append(vertex_shader_code, "}\n");
} else if (state.vertex_program) {
vertex_shader_code = vsh_translate(VSH_VERSION_XVS,
(uint32_t*)state.program_data,
state.program_length,
v_prefix);
} else {
assert(false);
}
if (vertex_shader_code) {
const char* vertex_shader_code_str = qstring_get_str(vertex_shader_code);
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glAttachShader(program, vertex_shader);
glShaderSource(vertex_shader, 1, &vertex_shader_code_str, 0);
glCompileShader(vertex_shader);
NV2A_DPRINTF("bind new vertex shader, code:\n%s\n", vertex_shader_code_str);
/* Check it compiled */
compiled = 0;
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLchar log[1024];
glGetShaderInfoLog(vertex_shader, 1024, NULL, log);
fprintf(stderr, "nv2a: vertex shader compilation failed: %s\n", log);
abort();
}
QDECREF(vertex_shader_code);
}
if (state.fixed_function) {
/* bind fixed function vertex attributes */
glBindAttribLocation(program, NV2A_VERTEX_ATTR_POSITION, "position");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_WEIGHT, "weight");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_DIFFUSE, "diffuse");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_SPECULAR, "specular");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_FOG, "fog");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_BACK_DIFFUSE, "backDiffuse");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_BACK_SPECULAR, "backSpecular");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_TEXTURE0, "texture0");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_TEXTURE1, "texture1");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_TEXTURE2, "texture2");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_TEXTURE3, "texture3");
} else if (state.vertex_program) {
/* Bind attributes for transform program*/
char tmp[8];
for(i = 0; i < 16; i++) {
snprintf(tmp, sizeof(tmp), "v%d", i);
glBindAttribLocation(program, i, tmp);
}
} else {
assert(false);
}
/* generate a fragment shader from register combiners */
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glAttachShader(program, fragment_shader);
QString *fragment_shader_code = psh_translate(state.combiner_control,
state.shader_stage_program,
state.other_stage_input,
state.rgb_inputs, state.rgb_outputs,
state.alpha_inputs, state.alpha_outputs,
/* constant_0, constant_1, */
state.final_inputs_0, state.final_inputs_1,
/* final_constant_0, final_constant_1, */
state.rect_tex,
state.compare_mode,
state.alphakill,
state.alpha_test, state.alpha_func);
const char *fragment_shader_code_str = qstring_get_str(fragment_shader_code);
NV2A_DPRINTF("bind new fragment shader, code:\n%s\n", fragment_shader_code_str);
glShaderSource(fragment_shader, 1, &fragment_shader_code_str, 0);
glCompileShader(fragment_shader);
/* Check it compiled */
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLchar log[1024];
glGetShaderInfoLog(fragment_shader, 1024, NULL, log);
fprintf(stderr, "nv2a: fragment shader compilation failed: %s\n", log);
abort();
}
QDECREF(fragment_shader_code);
if (with_geom) {
GLuint geometry_shader = glCreateShader(GL_GEOMETRY_SHADER);
glAttachShader(program, geometry_shader);
QString* geometry_shader_code =
generate_geometry_shader(state.primitive_mode);
const char* geometry_shader_code_str =
qstring_get_str(geometry_shader_code);
NV2A_DPRINTF("bind geometry shader, code:\n%s\n", geometry_shader_code_str);
glShaderSource(geometry_shader, 1, &geometry_shader_code_str, 0);
glCompileShader(geometry_shader);
/* Check it compiled */
compiled = 0;
glGetShaderiv(geometry_shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLchar log[2048];
glGetShaderInfoLog(geometry_shader, 2048, NULL, log);
fprintf(stderr, "nv2a: geometry shader compilation failed: %s\n", log);
abort();
}
QDECREF(geometry_shader_code);
}
/* link the program */
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if(!linked) {
GLchar log[2048];
glGetProgramInfoLog(program, 2048, NULL, log);
fprintf(stderr, "nv2a: shader linking failed: %s\n", log);
abort();
}
glUseProgram(program);
/* set texture samplers */
for (i = 0; i < NV2A_MAX_TEXTURES; i++) {
char samplerName[16];
snprintf(samplerName, sizeof(samplerName), "texSamp%d", i);
GLint texSampLoc = glGetUniformLocation(program, samplerName);
if (texSampLoc >= 0) {
glUniform1i(texSampLoc, i);
}
}
/* validate the program */
glValidateProgram(program);
GLint valid = 0;
glGetProgramiv(program, GL_VALIDATE_STATUS, &valid);
if (!valid) {
GLchar log[1024];
glGetProgramInfoLog(program, 1024, NULL, log);
fprintf(stderr, "nv2a: shader validation failed: %s\n", log);
abort();
}
ShaderBinding* ret = g_malloc0(sizeof(ShaderBinding));
ret->gl_program = program;
/* lookup fragment shader locations */
for (i=0; i<=8; i++) {
for (j=0; j<2; j++) {
char tmp[8];
snprintf(tmp, sizeof(tmp), "c_%d_%d", i, j);
ret->psh_constant_loc[i][j] = glGetUniformLocation(program, tmp);
}
}
if (state.vertex_program) {
/* lookup vertex shader bindings */
for(i = 0; i < NV2A_VERTEXSHADER_CONSTANTS; i++) {
char tmp[8];
snprintf(tmp, sizeof(tmp), "c[%d]", i);
ret->vsh_constant_loc[i] = glGetUniformLocation(program, tmp);
}
}
return ret;
}
static void pgraph_apply_anti_aliasing_factor(PGRAPHState *pg,
unsigned int *width,
unsigned int *height)
@ -3228,6 +2680,17 @@ static void pgraph_get_surface_dimensions(PGRAPHState *pg,
}
}
/* hash and equality for shader cache hash table */
static guint shader_hash(gconstpointer key)
{
return fnv_hash(key, sizeof(ShaderState));
}
static gboolean shader_equal(gconstpointer a, gconstpointer b)
{
const ShaderState *as = a, *bs = b;
return memcmp(as, bs, sizeof(ShaderState)) == 0;
}
static void pgraph_bind_shaders(PGRAPHState *pg)
{
int i, j;
@ -5502,7 +4965,7 @@ static void pgraph_method(NV2AState *d,
if (pg->draw_arrays_length > 0) {
unsigned int last_start =
pg->gl_draw_arrays_start[pg->draw_arrays_length - 1];
unsigned int* last_count =
GLsizei* last_count =
&pg->gl_draw_arrays_count[pg->draw_arrays_length - 1];
if (start == (last_start + *last_count)) {
*last_count += count;

523
hw/xbox/nv2a_shaders.c Normal file
View File

@ -0,0 +1,523 @@
/*
* QEMU Geforce NV2A shader generator
*
* Copyright (c) 2015 espes
* Copyright (c) 2015 JayFoxRox
*
* 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 or
* (at your option) version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "qemu-common.h"
#include "hw/xbox/nv2a_shaders.h"
// #define DEBUG
#ifdef DEBUG
# define NV2A_DPRINTF(format, ...) printf("nv2a: " format, ## __VA_ARGS__)
#else
# define NV2A_DPRINTF(format, ...) do { } while (0)
#endif
static void generate_geometry_shader_pass_vertex(QString* s, const char* v)
{
qstring_append_fmt(s, " gD0 = vD0[%s];\n", v);
qstring_append_fmt(s, " gD1 = vD1[%s];\n", v);
qstring_append_fmt(s, " gB0 = vB0[%s];\n", v);
qstring_append_fmt(s, " gB1 = vB1[%s];\n", v);
qstring_append_fmt(s, " gFog = vFog[%s];\n", v);
qstring_append_fmt(s, " gT0 = vT0[%s];\n", v);
qstring_append_fmt(s, " gT1 = vT1[%s];\n", v);
qstring_append_fmt(s, " gT2 = vT2[%s];\n", v);
qstring_append_fmt(s, " gT3 = vT3[%s];\n", v);
qstring_append_fmt(s, " gPos_w = vPos_w[%s];\n", v);
qstring_append_fmt(s, " gl_Position = gl_in[%s].gl_Position;\n", v);
qstring_append(s, " EmitVertex();\n");
}
static QString* generate_geometry_shader(enum ShaderPrimitiveMode primitive_mode)
{
/* generate a geometry shader to support deprecated primitive types */
QString* s = qstring_new();
qstring_append(s, "#version 330\n");
qstring_append(s, "\n");
switch (primitive_mode) {
case PRIM_TYPE_QUADS:
qstring_append(s, "layout(lines_adjacency) in;\n");
qstring_append(s, "layout(triangle_strip, max_vertices = 4) out;\n");
break;
default:
assert(false);
break;
}
qstring_append(s, "\n");
qstring_append(s, "noperspective in float vPos_w[];\n");
qstring_append(s, "noperspective in vec4 vD0[];\n");
qstring_append(s, "noperspective in vec4 vD1[];\n");
qstring_append(s, "noperspective in vec4 vB0[];\n");
qstring_append(s, "noperspective in vec4 vB1[];\n");
qstring_append(s, "noperspective in vec4 vFog[];\n");
qstring_append(s, "noperspective in vec4 vT0[];\n");
qstring_append(s, "noperspective in vec4 vT1[];\n");
qstring_append(s, "noperspective in vec4 vT2[];\n");
qstring_append(s, "noperspective in vec4 vT3[];\n");
qstring_append(s, "\n");
qstring_append(s, "noperspective out float gPos_w;\n");
qstring_append(s, "noperspective out vec4 gD0;\n");
qstring_append(s, "noperspective out vec4 gD1;\n");
qstring_append(s, "noperspective out vec4 gB0;\n");
qstring_append(s, "noperspective out vec4 gB1;\n");
qstring_append(s, "noperspective out vec4 gFog;\n");
qstring_append(s, "noperspective out vec4 gT0;\n");
qstring_append(s, "noperspective out vec4 gT1;\n");
qstring_append(s, "noperspective out vec4 gT2;\n");
qstring_append(s, "noperspective out vec4 gT3;\n");
qstring_append(s, "\n");
qstring_append(s, "void main() {\n");
switch (primitive_mode) {
case PRIM_TYPE_QUADS:
generate_geometry_shader_pass_vertex(s, "0");
generate_geometry_shader_pass_vertex(s, "1");
generate_geometry_shader_pass_vertex(s, "3");
generate_geometry_shader_pass_vertex(s, "2");
qstring_append(s, "EndPrimitive();\n");
break;
default:
assert(false);
break;
}
qstring_append(s, "}\n");
return s;
}
static QString* generate_fixed_function(const ShaderState state, char v_prefix)
{
int i, j;
/* generate vertex shader mimicking fixed function */
QString* vertex_shader_code = qstring_new();
qstring_append(vertex_shader_code,
"#version 330\n"
"\n"
"in vec4 position;\n"
"in vec4 weight;\n"
"in vec3 normal;\n"
"in vec4 diffuse;\n"
"in vec4 specular;\n"
"in float fogCoord;\n"
"in vec4 backDiffuse;\n"
"in vec4 backSpecular;\n"
"in vec4 texture0;\n"
"in vec4 texture1;\n"
"in vec4 texture2;\n"
"in vec4 texture3;\n"
"\n");
qstring_append_fmt(vertex_shader_code,
"noperspective out float %cPos_w;\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cD0 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cD1 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cB0 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cB1 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cFog = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cT0 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cT1 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cT2 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"noperspective out vec4 %cT3 = vec4(0.0,0.0,0.0,1.0);\n", v_prefix);
qstring_append(vertex_shader_code,
"\n"
/* FIXME: Add these uniforms using code when they are used */
"uniform mat4 texMat0;\n"
"uniform mat4 texMat1;\n"
"uniform mat4 texMat2;\n"
"uniform mat4 texMat3;\n"
"uniform mat4 modelViewMat0;\n"
"uniform mat4 modelViewMat1;\n"
"uniform mat4 modelViewMat2;\n"
"uniform mat4 modelViewMat3;\n"
"uniform mat4 invModelViewMat0;\n"
"uniform mat4 invModelViewMat1;\n"
"uniform mat4 invModelViewMat2;\n"
"uniform mat4 invModelViewMat3;\n"
"uniform mat4 projectionMat; /* FIXME: when is this used? */\n"
"uniform mat4 compositeMat;\n"
"uniform mat4 invViewport;\n"
"\n"
"void main() {\n");
/* Skinning */
unsigned int count;
bool mix;
switch (state.skinning) {
case SKINNING_OFF:
count = 0; break;
case SKINNING_1WEIGHTS:
mix = true; count = 2; break;
case SKINNING_2WEIGHTS:
mix = true; count = 3; break;
case SKINNING_3WEIGHTS:
mix = true; count = 4; break;
case SKINNING_2WEIGHTS2MATRICES:
mix = false; count = 2; break;
case SKINNING_3WEIGHTS3MATRICES:
mix = false; count = 3; break;
case SKINNING_4WEIGHTS4MATRICES:
mix = false; count = 4; break;
default:
assert(false);
break;
}
qstring_append_fmt(vertex_shader_code, "/* Skinning mode %d */\n",
state.skinning);
if (count == 0) {
qstring_append(vertex_shader_code, "vec4 tPosition = position * modelViewMat0;\n");
/* FIXME: Is the normal still transformed? */
qstring_append(vertex_shader_code, "vec3 tNormal = (vec4(normal, 0.0) * invModelViewMat0).xyz;\n");
} else {
qstring_append(vertex_shader_code, "vec4 tPosition = vec4(0.0);\n");
qstring_append(vertex_shader_code, "vec3 tNormal = vec3(0.0);\n");
if (mix) {
/* Tweening */
if (count == 2) {
qstring_append(vertex_shader_code,
"tPosition += mix(position * modelViewMat1,\n"
" position * modelViewMat0, weight.x);\n"
"tNormal += mix((vec4(normal, 0.0) * invModelViewMat1).xyz,\n"
" (vec4(normal, 0.0) * invModelViewMat0).xyz, weight.x);\n");
} else {
/* FIXME: Not sure how blend weights are calculated */
assert(false);
}
} else {
/* Individual matrices */
for (i = 0; i < count; i++) {
char c = "xyzw"[i];
qstring_append_fmt(vertex_shader_code,
"tPosition += position * modelViewMat%d * weight.%c;\n",
i, c);
qstring_append_fmt(vertex_shader_code,
"tNormal += (vec4(normal, 0.0) * invModelViewMat%d * weight.%c).xyz;\n",
i, c);
}
assert(false); /* FIXME: Untested */
}
}
/* Normalization */
if (state.normalization) {
qstring_append(vertex_shader_code, "tNormal = normalize(tNormal);\n");
}
/* Texgen */
for (i = 0; i < 4; i++) {
qstring_append_fmt(vertex_shader_code, "/* Texgen for stage %d */\n",
i);
qstring_append_fmt(vertex_shader_code, "vec4 tTexture%d;\n",
i);
/* Set each component individually */
/* FIXME: could be nicer if some channels share the same texgen */
for (j = 0; j < 4; j++) {
char c = "xyzw"[j];
switch (state.texgen[i][j]) {
case TEXGEN_DISABLE:
qstring_append_fmt(vertex_shader_code,
"tTexture%d.%c = texture%d.%c;\n",
i, c, i, c);
break;
case TEXGEN_EYE_LINEAR:
qstring_append_fmt(vertex_shader_code,
"tTexture%d.%c = tPosition.%c;\n",
i, c, c);
break;
case TEXGEN_OBJECT_LINEAR:
qstring_append_fmt(vertex_shader_code,
"tTexture%d.%c = position.%c;\n",
i, c, c);
break;
case TEXGEN_SPHERE_MAP:
assert(i < 2); /* Channels S,T only! */
assert(false);
break;
case TEXGEN_REFLECTION_MAP:
assert(i < 3); /* Channels S,T,R only! */
qstring_append_fmt(vertex_shader_code,
"tTexture%d.%c = reflect(???, tNormal).%c;\n",
i, c, c);
assert(false); /* FIXME: Code not complete yet! */
break;
case TEXGEN_NORMAL_MAP:
assert(i < 3); /* Channels S,T,R only! */
qstring_append_fmt(vertex_shader_code,
"tTexture%d.%c = tNormal.%c;\n",
i, c, c);
break;
default:
assert(false);
break;
}
}
}
/* Apply texture matrices */
for (i = 0; i < 4; i++) {
if (state.texture_matrix_enable[i]) {
qstring_append_fmt(vertex_shader_code,
"tTexture%d = tTexture%d * texMat%d;\n",
i, i, i);
}
}
/* If skinning is off the composite matrix already includes the MV matrix */
if (state.skinning == SKINNING_OFF) {
qstring_append(vertex_shader_code, "tPosition = position;\n");
}
qstring_append(vertex_shader_code,
" gl_Position = invViewport * (tPosition * compositeMat);\n"
/* temp hack: the composite matrix includes the view transform... */
//" gl_Position = position * compositeMat;\n"
//" gl_Position.x = (gl_Position.x - 320.0) / 320.0;\n"
//" gl_Position.y = -(gl_Position.y - 240.0) / 240.0;\n"
" gl_Position.z = gl_Position.z * 2.0 - gl_Position.w;\n");
qstring_append_fmt(vertex_shader_code,
"%cPos_w = 1.0/gl_Position.w;\n", v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cD0 = diffuse * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cD1 = specular * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cB0 = backDiffuse * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cB1 = backSpecular * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cT0 = tTexture0 * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cT1 = tTexture1 * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cT2 = tTexture2 * %cPos_w;\n", v_prefix, v_prefix);
qstring_append_fmt(vertex_shader_code,
"%cT3 = tTexture3 * %cPos_w;\n", v_prefix, v_prefix);
qstring_append(vertex_shader_code, "}\n");
return vertex_shader_code;
}
ShaderBinding* generate_shaders(const ShaderState state)
{
int i, j;
GLint compiled = 0;
bool with_geom = state.primitive_mode == PRIM_TYPE_QUADS;
char v_prefix = with_geom ? 'v' : 'g';
GLuint program = glCreateProgram();
/* create the vertex shader */
QString *vertex_shader_code = NULL;
if (state.fixed_function) {
vertex_shader_code = generate_fixed_function(state, v_prefix);
} else if (state.vertex_program) {
vertex_shader_code = vsh_translate(VSH_VERSION_XVS,
(uint32_t*)state.program_data,
state.program_length,
v_prefix);
} else {
assert(false);
}
if (vertex_shader_code) {
const char* vertex_shader_code_str = qstring_get_str(vertex_shader_code);
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glAttachShader(program, vertex_shader);
glShaderSource(vertex_shader, 1, &vertex_shader_code_str, 0);
glCompileShader(vertex_shader);
NV2A_DPRINTF("bind new vertex shader, code:\n%s\n", vertex_shader_code_str);
/* Check it compiled */
compiled = 0;
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLchar log[1024];
glGetShaderInfoLog(vertex_shader, 1024, NULL, log);
fprintf(stderr, "nv2a: vertex shader compilation failed: %s\n", log);
abort();
}
QDECREF(vertex_shader_code);
}
if (state.fixed_function) {
/* bind fixed function vertex attributes */
glBindAttribLocation(program, NV2A_VERTEX_ATTR_POSITION, "position");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_WEIGHT, "weight");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_DIFFUSE, "diffuse");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_SPECULAR, "specular");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_FOG, "fog");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_BACK_DIFFUSE, "backDiffuse");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_BACK_SPECULAR, "backSpecular");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_TEXTURE0, "texture0");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_TEXTURE1, "texture1");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_TEXTURE2, "texture2");
glBindAttribLocation(program, NV2A_VERTEX_ATTR_TEXTURE3, "texture3");
} else if (state.vertex_program) {
/* Bind attributes for transform program*/
char tmp[8];
for(i = 0; i < 16; i++) {
snprintf(tmp, sizeof(tmp), "v%d", i);
glBindAttribLocation(program, i, tmp);
}
} else {
assert(false);
}
/* generate a fragment shader from register combiners */
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glAttachShader(program, fragment_shader);
QString *fragment_shader_code = psh_translate(state.combiner_control,
state.shader_stage_program,
state.other_stage_input,
state.rgb_inputs, state.rgb_outputs,
state.alpha_inputs, state.alpha_outputs,
/* constant_0, constant_1, */
state.final_inputs_0, state.final_inputs_1,
/* final_constant_0, final_constant_1, */
state.rect_tex,
state.compare_mode,
state.alphakill,
state.alpha_test, state.alpha_func);
const char *fragment_shader_code_str = qstring_get_str(fragment_shader_code);
NV2A_DPRINTF("bind new fragment shader, code:\n%s\n", fragment_shader_code_str);
glShaderSource(fragment_shader, 1, &fragment_shader_code_str, 0);
glCompileShader(fragment_shader);
/* Check it compiled */
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLchar log[1024];
glGetShaderInfoLog(fragment_shader, 1024, NULL, log);
fprintf(stderr, "nv2a: fragment shader compilation failed: %s\n", log);
abort();
}
QDECREF(fragment_shader_code);
if (with_geom) {
GLuint geometry_shader = glCreateShader(GL_GEOMETRY_SHADER);
glAttachShader(program, geometry_shader);
QString* geometry_shader_code =
generate_geometry_shader(state.primitive_mode);
const char* geometry_shader_code_str =
qstring_get_str(geometry_shader_code);
NV2A_DPRINTF("bind geometry shader, code:\n%s\n", geometry_shader_code_str);
glShaderSource(geometry_shader, 1, &geometry_shader_code_str, 0);
glCompileShader(geometry_shader);
/* Check it compiled */
compiled = 0;
glGetShaderiv(geometry_shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLchar log[2048];
glGetShaderInfoLog(geometry_shader, 2048, NULL, log);
fprintf(stderr, "nv2a: geometry shader compilation failed: %s\n", log);
abort();
}
QDECREF(geometry_shader_code);
}
/* link the program */
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if(!linked) {
GLchar log[2048];
glGetProgramInfoLog(program, 2048, NULL, log);
fprintf(stderr, "nv2a: shader linking failed: %s\n", log);
abort();
}
glUseProgram(program);
/* set texture samplers */
for (i = 0; i < 4; i++) {
char samplerName[16];
snprintf(samplerName, sizeof(samplerName), "texSamp%d", i);
GLint texSampLoc = glGetUniformLocation(program, samplerName);
if (texSampLoc >= 0) {
glUniform1i(texSampLoc, i);
}
}
/* validate the program */
glValidateProgram(program);
GLint valid = 0;
glGetProgramiv(program, GL_VALIDATE_STATUS, &valid);
if (!valid) {
GLchar log[1024];
glGetProgramInfoLog(program, 1024, NULL, log);
fprintf(stderr, "nv2a: shader validation failed: %s\n", log);
abort();
}
ShaderBinding* ret = g_malloc0(sizeof(ShaderBinding));
ret->gl_program = program;
/* lookup fragment shader locations */
for (i=0; i<=8; i++) {
for (j=0; j<2; j++) {
char tmp[8];
snprintf(tmp, sizeof(tmp), "c_%d_%d", i, j);
ret->psh_constant_loc[i][j] = glGetUniformLocation(program, tmp);
}
}
if (state.vertex_program) {
/* lookup vertex shader bindings */
for(i = 0; i < NV2A_VERTEXSHADER_CONSTANTS; i++) {
char tmp[8];
snprintf(tmp, sizeof(tmp), "c[%d]", i);
ret->vsh_constant_loc[i] = glGetUniformLocation(program, tmp);
}
}
return ret;
}

104
hw/xbox/nv2a_shaders.h Normal file
View File

@ -0,0 +1,104 @@
/*
* QEMU Geforce NV2A shader generator
*
* Copyright (c) 2015 espes
* Copyright (c) 2015 JayFoxRox
*
* 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 or
* (at your option) version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "qapi/qmp/qstring.h"
#include "gl/gloffscreen.h"
#include "nv2a_vsh.h"
#include "nv2a_psh.h"
#define NV2A_VERTEX_ATTR_POSITION 0
#define NV2A_VERTEX_ATTR_WEIGHT 1
#define NV2A_VERTEX_ATTR_NORMAL 2
#define NV2A_VERTEX_ATTR_DIFFUSE 3
#define NV2A_VERTEX_ATTR_SPECULAR 4
#define NV2A_VERTEX_ATTR_FOG 5
#define NV2A_VERTEX_ATTR_POINT_SIZE 6
#define NV2A_VERTEX_ATTR_BACK_DIFFUSE 7
#define NV2A_VERTEX_ATTR_BACK_SPECULAR 8
#define NV2A_VERTEX_ATTR_TEXTURE0 9
#define NV2A_VERTEX_ATTR_TEXTURE1 10
#define NV2A_VERTEX_ATTR_TEXTURE2 11
#define NV2A_VERTEX_ATTR_TEXTURE3 12
#define NV2A_VERTEX_ATTR_RESERVED1 13
#define NV2A_VERTEX_ATTR_RESERVED2 14
#define NV2A_VERTEX_ATTR_RESERVED3 15
#define NV2A_MAX_TRANSFORM_PROGRAM_LENGTH 136
#define NV2A_VERTEXSHADER_CONSTANTS 192
enum ShaderPrimitiveMode {
PRIM_TYPE_NONE,
PRIM_TYPE_POINTS,
PRIM_TYPE_LINES,
PRIM_TYPE_LINE_LOOP,
PRIM_TYPE_LINE_STRIP,
PRIM_TYPE_TRIANGLES,
PRIM_TYPE_TRIANGLE_STRIP,
PRIM_TYPE_TRIANGLE_FAN,
PRIM_TYPE_QUADS,
PRIM_TYPE_QUAD_STRIP,
PRIM_TYPE_POLYGON,
};
typedef struct ShaderState {
/* fragment shader - register combiner stuff */
uint32_t combiner_control;
uint32_t shader_stage_program;
uint32_t other_stage_input;
uint32_t final_inputs_0;
uint32_t final_inputs_1;
uint32_t rgb_inputs[8], rgb_outputs[8];
uint32_t alpha_inputs[8], alpha_outputs[8];
bool rect_tex[4];
bool compare_mode[4][4];
bool alphakill[4];
bool alpha_test;
enum PshAlphaFunc alpha_func;
bool texture_matrix_enable[4];
enum VshTexgen texgen[4][4];
enum VshSkinning skinning;
bool normalization;
bool fixed_function;
/* vertex program */
bool vertex_program;
uint32_t program_data[NV2A_MAX_TRANSFORM_PROGRAM_LENGTH][VSH_TOKEN_SIZE];
int program_length;
/* primitive format for geometry shader */
enum ShaderPrimitiveMode primitive_mode;
} ShaderState;
typedef struct ShaderBinding {
GLuint gl_program;
GLint psh_constant_loc[9][2];
GLint vsh_constant_loc[NV2A_VERTEXSHADER_CONSTANTS];
} ShaderBinding;
ShaderBinding* generate_shaders(const ShaderState state);