/************************************************************************** * * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas. * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * **************************************************************************/ /** * State validation for vertex/fragment shaders. * Note that we have to delay most vertex/fragment shader translation * until rendering time since the linkage between the vertex outputs and * fragment inputs can vary depending on the pairing of shaders. * * Authors: * Brian Paul */ #include "main/imports.h" #include "main/mtypes.h" #include "pipe/p_context.h" #include "pipe/tgsi/mesa/mesa_to_tgsi.h" #include "pipe/tgsi/exec/tgsi_core.h" #include "st_context.h" #include "st_cache.h" #include "st_atom.h" #include "st_program.h" #include "st_atom_shader.h" /** * Structure to describe a (vertex program, fragment program) pair * which is linked together (used together to render something). This * linkage basically servers the same purpose as the OpenGL Shading * Language linker, but also applies to ARB programs and Mesa's * fixed-function-generated programs. * * More background: * * The translation from Mesa programs to TGSI programs depends on the * linkage between the vertex program and the fragment program. This is * because we tightly pack the inputs and outputs of shaders into * consecutive "slots". * * Suppose an app uses one vertex program "VP" (outputting pos, color and tex0) * and two fragment programs: * FP1: uses tex0 input only (input slot 0) * FP2: uses color input only (input slot 0) * * When VP is used with FP1 we want VP.output[2] to match FP1.input[0], but * when VP is used with FP2 we want VP.output[1] to match FP1.input[0]. * * We don't want to re-translate the vertex and/or fragment programs * each time the VP/FP bindings/linkings change. The solution is this * structure which stores the translated TGSI shaders on a per-linkage * basis. * */ struct linked_program_pair { struct st_vertex_program *vprog; /**< never changes */ struct st_fragment_program *fprog; /**< never changes */ struct tgsi_token vs_tokens[ST_FP_MAX_TOKENS]; struct tgsi_token fs_tokens[ST_FP_MAX_TOKENS]; const struct cso_vertex_shader *vs; const struct cso_fragment_shader *fs; GLuint vertSerialNo, fragSerialNo; /** maps a Mesa VERT_ATTRIB_x to a packed TGSI input index */ GLuint vp_input_to_index[MAX_VERTEX_PROGRAM_ATTRIBS]; /** maps a TGSI input index back to a Mesa VERT_ATTRIB_x */ GLuint vp_index_to_input[MAX_VERTEX_PROGRAM_ATTRIBS]; GLuint vp_result_to_slot[VERT_RESULT_MAX]; struct linked_program_pair *next; }; /** XXX temporary - use some kind of hash table instead */ static struct linked_program_pair *Pairs = NULL; static void find_and_remove(struct gl_program *prog) { struct linked_program_pair *pair, *prev = NULL, *next; for (pair = Pairs; pair; pair = next) { next = pair->next; if (pair->vprog == (struct st_vertex_program *) prog || pair->fprog == (struct st_fragment_program *) prog) { /* unlink */ if (prev) prev->next = next; else Pairs = next; /* delete pair->vs */ /* delete pair->fs */ free(pair); } else { prev = pair; } } } /** * Delete any known program pairs that use the given vertex program. */ void st_remove_vertex_program(struct st_context *st, struct st_vertex_program *stvp) { find_and_remove(&stvp->Base.Base); } /** * Delete any known program pairs that use the given fragment program. */ void st_remove_fragment_program(struct st_context *st, struct st_fragment_program *stfp) { find_and_remove(&stfp->Base.Base); } /** * Given a vertex program output attribute, return the corresponding * fragment program input attribute. * \return -1 for vertex outputs that have no corresponding fragment input */ static GLint vp_out_to_fp_in(GLuint vertResult) { if (vertResult >= VERT_RESULT_TEX0 && vertResult < VERT_RESULT_TEX0 + MAX_TEXTURE_COORD_UNITS) return FRAG_ATTRIB_TEX0 + (vertResult - VERT_RESULT_TEX0); if (vertResult >= VERT_RESULT_VAR0 && vertResult < VERT_RESULT_VAR0 + MAX_VARYING) return FRAG_ATTRIB_VAR0 + (vertResult - VERT_RESULT_VAR0); switch (vertResult) { case VERT_RESULT_HPOS: return FRAG_ATTRIB_WPOS; case VERT_RESULT_COL0: return FRAG_ATTRIB_COL0; case VERT_RESULT_COL1: return FRAG_ATTRIB_COL1; case VERT_RESULT_FOGC: return FRAG_ATTRIB_FOGC; default: /* Back-face colors, edge flags, etc */ return -1; } } /** * Examine the outputs written by a vertex program and the inputs read * by a fragment program to determine which match up and where they * should be mapped into the generic shader output/input slots. * \param vert_output_map returns the vertex output register mapping * \param frag_input_map returns the fragment input register mapping */ static GLuint link_outputs_to_inputs(GLbitfield outputsWritten, GLbitfield inputsRead, GLuint vert_output_map[], GLuint frag_input_map[]) { static const GLuint UNUSED = ~0; GLint vert_slot_to_attr[50], frag_slot_to_attr[50]; GLuint outAttr, inAttr; GLuint numIn = 0, dummySlot; for (inAttr = 0; inAttr < FRAG_ATTRIB_MAX; inAttr++) { if (inputsRead & (1 << inAttr)) { frag_input_map[inAttr] = numIn; frag_slot_to_attr[numIn] = inAttr; numIn++; } else { frag_input_map[inAttr] = UNUSED; } } for (outAttr = 0; outAttr < VERT_RESULT_MAX; outAttr++) { if (outputsWritten & (1 << outAttr)) { /* see if the frag prog wants this vert output */ GLint fpIn = vp_out_to_fp_in(outAttr); if (fpIn >= 0) { GLuint frag_slot = frag_input_map[fpIn]; vert_output_map[outAttr] = frag_slot; vert_slot_to_attr[frag_slot] = outAttr; } else { vert_output_map[outAttr] = UNUSED; } } else { vert_output_map[outAttr] = UNUSED; } } /* * We'll map all unused vertex program outputs to this slot. * We'll also map all undefined fragment program inputs to this slot. */ dummySlot = numIn; /* Map vert program outputs that aren't used to the dummy slot */ for (outAttr = 0; outAttr < VERT_RESULT_MAX; outAttr++) { if (outputsWritten & (1 << outAttr)) { if (vert_output_map[outAttr] == UNUSED) vert_output_map[outAttr] = dummySlot; } } /* Map frag program inputs that aren't defined to the dummy slot */ for (inAttr = 0; inAttr < FRAG_ATTRIB_MAX; inAttr++) { if (inputsRead & (1 << inAttr)) { if (frag_input_map[inAttr] == UNUSED) frag_input_map[inAttr] = dummySlot; } } #if 0 printf("vOut W slot\n"); for (outAttr = 0; outAttr < VERT_RESULT_MAX; outAttr++) { printf("%4d %c %4d\n", outAttr, " *"[(outputsWritten >> outAttr) & 1], vert_output_map[outAttr]); } printf("vIn R slot\n"); for (inAttr = 0; inAttr < FRAG_ATTRIB_MAX; inAttr++) { printf("%3d %c %4d\n", inAttr, " *"[(inputsRead >> inAttr) & 1], frag_input_map[inAttr]); } #endif return numIn; } static struct linked_program_pair * lookup_program_pair(struct st_context *st, struct st_vertex_program *vprog, struct st_fragment_program *fprog) { struct linked_program_pair *pair; /* search */ for (pair = Pairs; pair; pair = pair->next) { if (pair->vprog == vprog && pair->fprog == fprog) { /* found it */ break; } } /* * Examine the outputs of the vertex shader and the inputs of the * fragment shader to determine how to match both to a common set * of slots. */ if (!pair) { pair = CALLOC_STRUCT(linked_program_pair); if (pair) { pair->vprog = vprog; pair->fprog = fprog; } } return pair; } static void link_shaders(struct st_context *st, struct linked_program_pair *pair) { struct st_vertex_program *vprog = pair->vprog; struct st_fragment_program *fprog = pair->fprog; assert(vprog); assert(fprog); if (pair->vertSerialNo != vprog->serialNo || pair->fragSerialNo != fprog->serialNo) { /* re-link and re-translate */ GLuint vert_output_mapping[VERT_RESULT_MAX]; GLuint frag_input_mapping[FRAG_ATTRIB_MAX]; link_outputs_to_inputs(vprog->Base.Base.OutputsWritten, fprog->Base.Base.InputsRead | FRAG_BIT_WPOS, vert_output_mapping, frag_input_mapping); /* xlate vp to vs + vs tokens */ st_translate_vertex_program(st, vprog, vert_output_mapping, pair->vs_tokens, ST_FP_MAX_TOKENS); pair->vprog = vprog; /* temp hacks */ pair->vs = vprog->vs; vprog->vs = NULL; /* xlate fp to fs + fs tokens */ st_translate_fragment_program(st, fprog, frag_input_mapping, pair->fs_tokens, ST_FP_MAX_TOKENS); pair->fprog = fprog; /* temp hacks */ pair->fs = fprog->fs; fprog->fs = NULL; /* save pair */ pair->next = Pairs; Pairs = pair; pair->vertSerialNo = vprog->serialNo; pair->fragSerialNo = fprog->serialNo; } } static void update_linkage( struct st_context *st ) { struct linked_program_pair *pair; struct st_vertex_program *stvp; struct st_fragment_program *stfp; /* find active shader and params -- Should be covered by * ST_NEW_VERTEX_PROGRAM */ if (st->ctx->Shader.CurrentProgram && st->ctx->Shader.CurrentProgram->LinkStatus && st->ctx->Shader.CurrentProgram->VertexProgram) { struct gl_vertex_program *f = st->ctx->Shader.CurrentProgram->VertexProgram; stvp = st_vertex_program(f); } else { assert(st->ctx->VertexProgram._Current); stvp = st_vertex_program(st->ctx->VertexProgram._Current); } if (st->ctx->Shader.CurrentProgram && st->ctx->Shader.CurrentProgram->LinkStatus && st->ctx->Shader.CurrentProgram->FragmentProgram) { struct gl_fragment_program *f = st->ctx->Shader.CurrentProgram->FragmentProgram; stfp = st_fragment_program(f); } else { assert(st->ctx->FragmentProgram._Current); stfp = st_fragment_program(st->ctx->FragmentProgram._Current); } pair = lookup_program_pair(st, stvp, stfp); assert(pair); link_shaders(st, pair); /* Bind the vertex program and TGSI shader */ st->vp = stvp; st->state.vs = pair->vs; st->pipe->bind_vs_state(st->pipe, st->state.vs->data); /* Bind the fragment program and TGSI shader */ st->fp = stfp; st->state.fs = pair->fs; st->pipe->bind_fs_state(st->pipe, st->state.fs->data); st->vertex_result_to_slot = pair->vp_result_to_slot; } const struct st_tracked_state st_update_shader = { .name = "st_update_shader", .dirty = { .mesa = _NEW_PROGRAM, .st = ST_NEW_SHADER }, .update = update_linkage };