diff options
author | Tim Rowley <[email protected]> | 2017-03-02 16:41:02 -0600 |
---|---|---|
committer | Tim Rowley <[email protected]> | 2017-03-05 07:33:49 -0600 |
commit | f1d728411726fd0bc5baf9746e838f609900836e (patch) | |
tree | 14b54416bb159b66a71624042b66ae75faba318e /src/gallium/drivers/swr/swr_shader.cpp | |
parent | 08a82363ba7c281a1b3d7f6dbe6946860ee68e38 (diff) |
swr: implement geometry shaders
Reviewed-by: Bruce Cherniak <[email protected]>
Diffstat (limited to 'src/gallium/drivers/swr/swr_shader.cpp')
-rw-r--r-- | src/gallium/drivers/swr/swr_shader.cpp | 475 |
1 files changed, 470 insertions, 5 deletions
diff --git a/src/gallium/drivers/swr/swr_shader.cpp b/src/gallium/drivers/swr/swr_shader.cpp index 9169f6dc74e..09d8145a8f5 100644 --- a/src/gallium/drivers/swr/swr_shader.cpp +++ b/src/gallium/drivers/swr/swr_shader.cpp @@ -35,6 +35,7 @@ #include "tgsi/tgsi_strings.h" #include "util/u_format.h" +#include "util/u_prim.h" #include "gallivm/lp_bld_init.h" #include "gallivm/lp_bld_flow.h" #include "gallivm/lp_bld_struct.h" @@ -47,6 +48,7 @@ #include "swr_screen.h" using namespace SwrJit; +using namespace llvm; static unsigned locate_linkage(ubyte name, ubyte index, struct tgsi_shader_info *info); @@ -66,6 +68,11 @@ bool operator==(const swr_jit_fetch_key &lhs, const swr_jit_fetch_key &rhs) return !memcmp(&lhs, &rhs, sizeof(lhs)); } +bool operator==(const swr_jit_gs_key &lhs, const swr_jit_gs_key &rhs) +{ + return !memcmp(&lhs, &rhs, sizeof(lhs)); +} + static void swr_generate_sampler_key(const struct lp_tgsi_info &info, struct swr_context *ctx, @@ -137,11 +144,18 @@ swr_generate_fs_key(struct swr_jit_fs_key &key, key.nr_cbufs = ctx->framebuffer.nr_cbufs; key.light_twoside = ctx->rasterizer->light_twoside; key.sprite_coord_enable = ctx->rasterizer->sprite_coord_enable; + + struct tgsi_shader_info *pPrevShader; + if (ctx->gs) + pPrevShader = &ctx->gs->info.base; + else + pPrevShader = &ctx->vs->info.base; + memcpy(&key.vs_output_semantic_name, - &ctx->vs->info.base.output_semantic_name, + &pPrevShader->output_semantic_name, sizeof(key.vs_output_semantic_name)); memcpy(&key.vs_output_semantic_idx, - &ctx->vs->info.base.output_semantic_index, + &pPrevShader->output_semantic_index, sizeof(key.vs_output_semantic_idx)); swr_generate_sampler_key(swr_fs->info, ctx, PIPE_SHADER_FRAGMENT, key); @@ -171,6 +185,25 @@ swr_generate_fetch_key(struct swr_jit_fetch_key &key, key.fsState = velems->fsState; } +void +swr_generate_gs_key(struct swr_jit_gs_key &key, + struct swr_context *ctx, + swr_geometry_shader *swr_gs) +{ + memset(&key, 0, sizeof(key)); + + struct tgsi_shader_info *pPrevShader = &ctx->vs->info.base; + + memcpy(&key.vs_output_semantic_name, + &pPrevShader->output_semantic_name, + sizeof(key.vs_output_semantic_name)); + memcpy(&key.vs_output_semantic_idx, + &pPrevShader->output_semantic_index, + sizeof(key.vs_output_semantic_idx)); + + swr_generate_sampler_key(swr_gs->info, ctx, PIPE_SHADER_GEOMETRY, key); +} + struct BuilderSWR : public Builder { BuilderSWR(JitManager *pJitMgr, const char *pName) : Builder(pJitMgr) @@ -187,8 +220,433 @@ struct BuilderSWR : public Builder { struct gallivm_state *gallivm; PFN_VERTEX_FUNC CompileVS(struct swr_context *ctx, swr_jit_vs_key &key); PFN_PIXEL_KERNEL CompileFS(struct swr_context *ctx, swr_jit_fs_key &key); + PFN_GS_FUNC CompileGS(struct swr_context *ctx, swr_jit_gs_key &key); + + LLVMValueRef + swr_gs_llvm_fetch_input(const struct lp_build_tgsi_gs_iface *gs_iface, + struct lp_build_tgsi_context * bld_base, + boolean is_vindex_indirect, + LLVMValueRef vertex_index, + boolean is_aindex_indirect, + LLVMValueRef attrib_index, + LLVMValueRef swizzle_index); + void + swr_gs_llvm_emit_vertex(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef (*outputs)[4], + LLVMValueRef emitted_vertices_vec); + + void + swr_gs_llvm_end_primitive(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef verts_per_prim_vec, + LLVMValueRef emitted_prims_vec); + + void + swr_gs_llvm_epilogue(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef total_emitted_vertices_vec, + LLVMValueRef emitted_prims_vec); + }; +struct swr_gs_llvm_iface { + struct lp_build_tgsi_gs_iface base; + struct tgsi_shader_info *info; + + BuilderSWR *pBuilder; + + Value *pGsCtx; + SWR_GS_STATE *pGsState; + uint32_t num_outputs; + uint32_t num_verts_per_prim; + + Value *pVtxAttribMap; +}; + +// trampoline functions so we can use the builder llvm construction methods +static LLVMValueRef +swr_gs_llvm_fetch_input(const struct lp_build_tgsi_gs_iface *gs_iface, + struct lp_build_tgsi_context * bld_base, + boolean is_vindex_indirect, + LLVMValueRef vertex_index, + boolean is_aindex_indirect, + LLVMValueRef attrib_index, + LLVMValueRef swizzle_index) +{ + swr_gs_llvm_iface *iface = (swr_gs_llvm_iface*)gs_iface; + + return iface->pBuilder->swr_gs_llvm_fetch_input(gs_iface, bld_base, + is_vindex_indirect, + vertex_index, + is_aindex_indirect, + attrib_index, + swizzle_index); +} + +static void +swr_gs_llvm_emit_vertex(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef (*outputs)[4], + LLVMValueRef emitted_vertices_vec) +{ + swr_gs_llvm_iface *iface = (swr_gs_llvm_iface*)gs_base; + + iface->pBuilder->swr_gs_llvm_emit_vertex(gs_base, bld_base, + outputs, + emitted_vertices_vec); +} + +static void +swr_gs_llvm_end_primitive(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef verts_per_prim_vec, + LLVMValueRef emitted_prims_vec) +{ + swr_gs_llvm_iface *iface = (swr_gs_llvm_iface*)gs_base; + + iface->pBuilder->swr_gs_llvm_end_primitive(gs_base, bld_base, + verts_per_prim_vec, + emitted_prims_vec); +} + +static void +swr_gs_llvm_epilogue(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef total_emitted_vertices_vec, + LLVMValueRef emitted_prims_vec) +{ + swr_gs_llvm_iface *iface = (swr_gs_llvm_iface*)gs_base; + + iface->pBuilder->swr_gs_llvm_epilogue(gs_base, bld_base, + total_emitted_vertices_vec, + emitted_prims_vec); +} + +LLVMValueRef +BuilderSWR::swr_gs_llvm_fetch_input(const struct lp_build_tgsi_gs_iface *gs_iface, + struct lp_build_tgsi_context * bld_base, + boolean is_vindex_indirect, + LLVMValueRef vertex_index, + boolean is_aindex_indirect, + LLVMValueRef attrib_index, + LLVMValueRef swizzle_index) +{ + swr_gs_llvm_iface *iface = (swr_gs_llvm_iface*)gs_iface; + + IRB()->SetInsertPoint(unwrap(LLVMGetInsertBlock(gallivm->builder))); + + assert(is_vindex_indirect == false && is_aindex_indirect == false); + + Value *attrib = + LOAD(GEP(iface->pVtxAttribMap, {C(0), unwrap(attrib_index)})); + + Value *pInput = + LOAD(GEP(iface->pGsCtx, + {C(0), + C(SWR_GS_CONTEXT_vert), + unwrap(vertex_index), + C(0), + attrib, + unwrap(swizzle_index)})); + + return wrap(pInput); +} + +void +BuilderSWR::swr_gs_llvm_emit_vertex(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef (*outputs)[4], + LLVMValueRef emitted_vertices_vec) +{ + swr_gs_llvm_iface *iface = (swr_gs_llvm_iface*)gs_base; + SWR_GS_STATE *pGS = iface->pGsState; + + IRB()->SetInsertPoint(unwrap(LLVMGetInsertBlock(gallivm->builder))); + + const uint32_t simdVertexStride = sizeof(simdvertex); + const uint32_t numSimdBatches = (pGS->maxNumVerts + 7) / 8; + const uint32_t inputPrimStride = numSimdBatches * simdVertexStride; + + Value *pStream = LOAD(iface->pGsCtx, { 0, SWR_GS_CONTEXT_pStream }); + Value *vMask = LOAD(iface->pGsCtx, { 0, SWR_GS_CONTEXT_mask }); + Value *vMask1 = TRUNC(vMask, VectorType::get(mInt1Ty, 8)); + + Value *vOffsets = C({ + inputPrimStride * 0, + inputPrimStride * 1, + inputPrimStride * 2, + inputPrimStride * 3, + inputPrimStride * 4, + inputPrimStride * 5, + inputPrimStride * 6, + inputPrimStride * 7 } ); + + Value *vVertexSlot = ASHR(unwrap(emitted_vertices_vec), 3); + Value *vSimdSlot = AND(unwrap(emitted_vertices_vec), 7); + + for (uint32_t attrib = 0; attrib < iface->num_outputs; ++attrib) { + uint32_t attribSlot = attrib; + if (iface->info->output_semantic_name[attrib] == TGSI_SEMANTIC_PSIZE) + attribSlot = VERTEX_POINT_SIZE_SLOT; + else if (iface->info->output_semantic_name[attrib] == TGSI_SEMANTIC_PRIMID) + attribSlot = VERTEX_PRIMID_SLOT; + + Value *vOffsetsAttrib = + ADD(vOffsets, MUL(vVertexSlot, VIMMED1((uint32_t)sizeof(simdvertex)))); + vOffsetsAttrib = + ADD(vOffsetsAttrib, VIMMED1((uint32_t)(attribSlot*sizeof(simdvector)))); + vOffsetsAttrib = + ADD(vOffsetsAttrib, MUL(vSimdSlot, VIMMED1((uint32_t)sizeof(float)))); + + for (uint32_t channel = 0; channel < 4; ++channel) { + Value *vData = LOAD(unwrap(outputs[attrib][channel])); + Value *vPtrs = GEP(pStream, vOffsetsAttrib); + + vPtrs = BITCAST(vPtrs, + VectorType::get(PointerType::get(mFP32Ty, 0), 8)); + + MASKED_SCATTER(vData, vPtrs, 32, vMask1); + + vOffsetsAttrib = + ADD(vOffsetsAttrib, VIMMED1((uint32_t)sizeof(simdscalar))); + } + } +} + +void +BuilderSWR::swr_gs_llvm_end_primitive(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef verts_per_prim_vec, + LLVMValueRef emitted_prims_vec) +{ + swr_gs_llvm_iface *iface = (swr_gs_llvm_iface*)gs_base; + SWR_GS_STATE *pGS = iface->pGsState; + + IRB()->SetInsertPoint(unwrap(LLVMGetInsertBlock(gallivm->builder))); + + Value *pCutBuffer = + LOAD(iface->pGsCtx, {0, SWR_GS_CONTEXT_pCutOrStreamIdBuffer}); + Value *vMask = LOAD(iface->pGsCtx, { 0, SWR_GS_CONTEXT_mask }); + Value *vMask1 = TRUNC(vMask, VectorType::get(mInt1Ty, 8)); + + uint32_t vertsPerPrim = iface->num_verts_per_prim; + + Value *vCount = + ADD(MUL(unwrap(emitted_prims_vec), VIMMED1(vertsPerPrim)), + unwrap(verts_per_prim_vec)); + + struct lp_build_tgsi_soa_context *bld = lp_soa_context(bld_base); + vCount = LOAD(unwrap(bld->total_emitted_vertices_vec_ptr)); + + struct lp_exec_mask *exec_mask = &bld->exec_mask; + Value *mask = unwrap(lp_build_mask_value(bld->mask)); + if (exec_mask->has_mask) + mask = AND(mask, unwrap(exec_mask->exec_mask)); + + Value *cmpMask = VMASK(ICMP_NE(unwrap(verts_per_prim_vec), VIMMED1(0))); + mask = AND(mask, cmpMask); + vMask1 = TRUNC(mask, VectorType::get(mInt1Ty, 8)); + + const uint32_t cutPrimStride = + (pGS->maxNumVerts + JM()->mVWidth - 1) / JM()->mVWidth; + Value *vOffsets = C({ + (uint32_t)(cutPrimStride * 0), + (uint32_t)(cutPrimStride * 1), + (uint32_t)(cutPrimStride * 2), + (uint32_t)(cutPrimStride * 3), + (uint32_t)(cutPrimStride * 4), + (uint32_t)(cutPrimStride * 5), + (uint32_t)(cutPrimStride * 6), + (uint32_t)(cutPrimStride * 7) } ); + + vCount = SUB(vCount, VIMMED1(1)); + Value *vOffset = ADD(UDIV(vCount, VIMMED1(8)), vOffsets); + Value *vValue = SHL(VIMMED1(1), UREM(vCount, VIMMED1(8))); + + vValue = TRUNC(vValue, VectorType::get(mInt8Ty, 8)); + + Value *vPtrs = GEP(pCutBuffer, vOffset); + vPtrs = + BITCAST(vPtrs, VectorType::get(PointerType::get(mInt8Ty, 0), JM()->mVWidth)); + + Value *vGather = MASKED_GATHER(vPtrs, 32, vMask1); + vValue = OR(vGather, vValue); + MASKED_SCATTER(vValue, vPtrs, 32, vMask1); +} + +void +BuilderSWR::swr_gs_llvm_epilogue(const struct lp_build_tgsi_gs_iface *gs_base, + struct lp_build_tgsi_context * bld_base, + LLVMValueRef total_emitted_vertices_vec, + LLVMValueRef emitted_prims_vec) +{ + swr_gs_llvm_iface *iface = (swr_gs_llvm_iface*)gs_base; + SWR_GS_STATE *pGS = iface->pGsState; + + IRB()->SetInsertPoint(unwrap(LLVMGetInsertBlock(gallivm->builder))); + + STORE(unwrap(total_emitted_vertices_vec), iface->pGsCtx, {0, SWR_GS_CONTEXT_vertexCount}); +} + +PFN_GS_FUNC +BuilderSWR::CompileGS(struct swr_context *ctx, swr_jit_gs_key &key) +{ + SWR_GS_STATE *pGS = &ctx->gs->gsState; + struct tgsi_shader_info *info = &ctx->gs->info.base; + + pGS->gsEnable = true; + + pGS->numInputAttribs = info->num_inputs; + pGS->outputTopology = + swr_convert_prim_topology(info->properties[TGSI_PROPERTY_GS_OUTPUT_PRIM]); + pGS->maxNumVerts = info->properties[TGSI_PROPERTY_GS_MAX_OUTPUT_VERTICES]; + pGS->instanceCount = info->properties[TGSI_PROPERTY_GS_INVOCATIONS]; + + pGS->emitsRenderTargetArrayIndex = info->writes_layer; + pGS->emitsPrimitiveID = info->writes_primid; + pGS->emitsViewportArrayIndex = info->writes_viewport_index; + + // XXX: single stream for now... + pGS->isSingleStream = true; + pGS->singleStreamID = 0; + + struct swr_geometry_shader *gs = ctx->gs; + + LLVMValueRef inputs[PIPE_MAX_SHADER_INPUTS][TGSI_NUM_CHANNELS]; + LLVMValueRef outputs[PIPE_MAX_SHADER_OUTPUTS][TGSI_NUM_CHANNELS]; + + memset(outputs, 0, sizeof(outputs)); + + AttrBuilder attrBuilder; + attrBuilder.addStackAlignmentAttr(JM()->mVWidth * sizeof(float)); + AttributeSet attrSet = AttributeSet::get( + JM()->mContext, AttributeSet::FunctionIndex, attrBuilder); + + std::vector<Type *> gsArgs{PointerType::get(Gen_swr_draw_context(JM()), 0), + PointerType::get(Gen_SWR_GS_CONTEXT(JM()), 0)}; + FunctionType *vsFuncType = + FunctionType::get(Type::getVoidTy(JM()->mContext), gsArgs, false); + + // create new vertex shader function + auto pFunction = Function::Create(vsFuncType, + GlobalValue::ExternalLinkage, + "GS", + JM()->mpCurrentModule); + pFunction->addAttributes(AttributeSet::FunctionIndex, attrSet); + + BasicBlock *block = BasicBlock::Create(JM()->mContext, "entry", pFunction); + IRB()->SetInsertPoint(block); + LLVMPositionBuilderAtEnd(gallivm->builder, wrap(block)); + + auto argitr = pFunction->arg_begin(); + Value *hPrivateData = &*argitr++; + hPrivateData->setName("hPrivateData"); + Value *pGsCtx = &*argitr++; + pGsCtx->setName("gsCtx"); + + Value *consts_ptr = + GEP(hPrivateData, {C(0), C(swr_draw_context_constantGS)}); + consts_ptr->setName("gs_constants"); + Value *const_sizes_ptr = + GEP(hPrivateData, {0, swr_draw_context_num_constantsGS}); + const_sizes_ptr->setName("num_gs_constants"); + + struct lp_build_sampler_soa *sampler = + swr_sampler_soa_create(key.sampler, PIPE_SHADER_GEOMETRY); + + struct lp_bld_tgsi_system_values system_values; + memset(&system_values, 0, sizeof(system_values)); + system_values.prim_id = wrap(LOAD(pGsCtx, {0, SWR_GS_CONTEXT_PrimitiveID})); + system_values.instance_id = wrap(LOAD(pGsCtx, {0, SWR_GS_CONTEXT_InstanceID})); + + std::vector<Constant*> mapConstants; + Value *vtxAttribMap = ALLOCA(ArrayType::get(mInt32Ty, PIPE_MAX_SHADER_INPUTS)); + for (unsigned slot = 0; slot < info->num_inputs; slot++) { + ubyte semantic_name = info->input_semantic_name[slot]; + ubyte semantic_idx = info->input_semantic_index[slot]; + + unsigned vs_slot = + locate_linkage(semantic_name, semantic_idx, &ctx->vs->info.base) + 1; + + STORE(C(vs_slot), vtxAttribMap, {0, slot}); + mapConstants.push_back(C(vs_slot)); + } + + struct lp_build_mask_context mask; + Value *mask_val = LOAD(pGsCtx, {0, SWR_GS_CONTEXT_mask}, "gsMask"); + lp_build_mask_begin(&mask, gallivm, + lp_type_float_vec(32, 32 * 8), wrap(mask_val)); + + // zero out cut buffer so we can load/modify/store bits + MEMSET(LOAD(pGsCtx, {0, SWR_GS_CONTEXT_pCutOrStreamIdBuffer}), + C((char)0), + pGS->instanceCount * ((pGS->maxNumVerts + 7) / 8) * JM()->mVWidth, + sizeof(float) * KNOB_SIMD_WIDTH); + + struct swr_gs_llvm_iface gs_iface; + gs_iface.base.fetch_input = ::swr_gs_llvm_fetch_input; + gs_iface.base.emit_vertex = ::swr_gs_llvm_emit_vertex; + gs_iface.base.end_primitive = ::swr_gs_llvm_end_primitive; + gs_iface.base.gs_epilogue = ::swr_gs_llvm_epilogue; + gs_iface.pBuilder = this; + gs_iface.pGsCtx = pGsCtx; + gs_iface.pGsState = pGS; + gs_iface.num_outputs = gs->info.base.num_outputs; + gs_iface.num_verts_per_prim = + u_vertices_per_prim((pipe_prim_type)info->properties[TGSI_PROPERTY_GS_OUTPUT_PRIM]); + gs_iface.info = info; + gs_iface.pVtxAttribMap = vtxAttribMap; + + lp_build_tgsi_soa(gallivm, + gs->pipe.tokens, + lp_type_float_vec(32, 32 * 8), + &mask, + wrap(consts_ptr), + wrap(const_sizes_ptr), + &system_values, + inputs, + outputs, + wrap(hPrivateData), // (sampler context) + NULL, // thread data + sampler, + &gs->info.base, + &gs_iface.base); + + lp_build_mask_end(&mask); + + sampler->destroy(sampler); + + IRB()->SetInsertPoint(unwrap(LLVMGetInsertBlock(gallivm->builder))); + + RET_VOID(); + + gallivm_verify_function(gallivm, wrap(pFunction)); + gallivm_compile_module(gallivm); + + PFN_GS_FUNC pFunc = + (PFN_GS_FUNC)gallivm_jit_function(gallivm, wrap(pFunction)); + + debug_printf("geom shader %p\n", pFunc); + assert(pFunc && "Error: GeomShader = NULL"); + + JM()->mIsModuleFinalized = true; + + return pFunc; +} + +PFN_GS_FUNC +swr_compile_gs(struct swr_context *ctx, swr_jit_gs_key &key) +{ + BuilderSWR builder( + reinterpret_cast<JitManager *>(swr_screen(ctx->pipe.screen)->hJitMgr), + "GS"); + PFN_GS_FUNC func = builder.CompileGS(ctx, key); + + ctx->gs->map.insert(std::make_pair(key, make_unique<VariantGS>(builder.gallivm, func))); + return func; +} + PFN_VERTEX_FUNC BuilderSWR::CompileVS(struct swr_context *ctx, swr_jit_vs_key &key) { @@ -396,6 +854,12 @@ BuilderSWR::CompileFS(struct swr_context *ctx, swr_jit_fs_key &key) { struct swr_fragment_shader *swr_fs = ctx->fs; + struct tgsi_shader_info *pPrevShader; + if (ctx->gs) + pPrevShader = &ctx->gs->info.base; + else + pPrevShader = &ctx->vs->info.base; + LLVMValueRef inputs[PIPE_MAX_SHADER_INPUTS][TGSI_NUM_CHANNELS]; LLVMValueRef outputs[PIPE_MAX_SHADER_OUTPUTS][TGSI_NUM_CHANNELS]; @@ -530,11 +994,12 @@ BuilderSWR::CompileFS(struct swr_context *ctx, swr_jit_fs_key &key) } unsigned linkedAttrib = - locate_linkage(semantic_name, semantic_idx, &ctx->vs->info.base); + locate_linkage(semantic_name, semantic_idx, pPrevShader); + if (semantic_name == TGSI_SEMANTIC_GENERIC && key.sprite_coord_enable & (1 << semantic_idx)) { /* we add an extra attrib to the backendState in swr_update_derived. */ - linkedAttrib = ctx->vs->info.base.num_outputs - 1; + linkedAttrib = pPrevShader->num_outputs - 1; swr_fs->pointSpriteMask |= (1 << linkedAttrib); } else if (linkedAttrib == 0xFFFFFFFF) { inputs[attrib][0] = wrap(VIMMED1(0.0f)); @@ -558,7 +1023,7 @@ BuilderSWR::CompileFS(struct swr_context *ctx, swr_jit_fs_key &key) Value *offset = NULL; if (semantic_name == TGSI_SEMANTIC_COLOR && key.light_twoside) { bcolorAttrib = locate_linkage( - TGSI_SEMANTIC_BCOLOR, semantic_idx, &ctx->vs->info.base); + TGSI_SEMANTIC_BCOLOR, semantic_idx, pPrevShader); /* Neither front nor back colors were available. Nothing to load. */ if (bcolorAttrib == 0xFFFFFFFF && linkedAttrib == 0xFFFFFFFF) continue; |