From 7cedd56d24b8bc1935b3d187adeadd0963ce4303 Mon Sep 17 00:00:00 2001 From: Fabian Bieler Date: Thu, 22 Mar 2007 17:32:48 +0100 Subject: [PATCH] wined3d: Implement linear fog with pixel shader. --- dlls/wined3d/arb_program_shader.c | 2 +- dlls/wined3d/directx.c | 8 +-- dlls/wined3d/drawprim.c | 2 +- dlls/wined3d/pixelshader.c | 36 ++++++++++- dlls/wined3d/state.c | 101 ++++++++++++++++++++++++++++-- 5 files changed, 136 insertions(+), 13 deletions(-) diff --git a/dlls/wined3d/arb_program_shader.c b/dlls/wined3d/arb_program_shader.c index f2c58ff00ad..d24a5f9798c 100644 --- a/dlls/wined3d/arb_program_shader.c +++ b/dlls/wined3d/arb_program_shader.c @@ -284,7 +284,7 @@ static void pshader_get_register_name( break; case WINED3DSPR_COLOROUT: if (reg == 0) - sprintf(regstr, "result.color"); + sprintf(regstr, "TMP_COLOR"); else { /* TODO: See GL_ARB_draw_buffers */ FIXME("Unsupported write to render target %u\n", reg); diff --git a/dlls/wined3d/directx.c b/dlls/wined3d/directx.c index 915d9389d7b..e0dbe572391 100644 --- a/dlls/wined3d/directx.c +++ b/dlls/wined3d/directx.c @@ -291,18 +291,18 @@ static void select_shader_max_constants( switch (ps_selected_mode) { case SHADER_GLSL: - /* Subtract the other potential uniforms from the max available (bools & ints). + /* Subtract the other potential uniforms from the max available (bools & ints), and 2 states for fog. * In theory the texbem instruction may need one more shader constant too. But lets assume * that a sm <= 1.3 shader does not need all the uniforms provided by a glsl-capable card, * and lets not take away a uniform needlessly from all other shaders. */ - gl_info->max_pshader_constantsF = gl_info->ps_glsl_constantsF - (MAX_CONST_B / 4) - MAX_CONST_I; + gl_info->max_pshader_constantsF = gl_info->ps_glsl_constantsF - (MAX_CONST_B / 4) - MAX_CONST_I - 2; break; case SHADER_ARB: /* The arb shader only loads the bump mapping environment matrix into the shader if it finds - * a free constant to do that, so no need to reduce the number of available constants. + * a free constant to do that, so only reduce the number of available constants by 2 for the fog states. */ - gl_info->max_pshader_constantsF = gl_info->ps_arb_constantsF; + gl_info->max_pshader_constantsF = gl_info->ps_arb_constantsF - 2; break; default: gl_info->max_pshader_constantsF = 0; diff --git a/dlls/wined3d/drawprim.c b/dlls/wined3d/drawprim.c index ec45543c11b..089030bef7a 100644 --- a/dlls/wined3d/drawprim.c +++ b/dlls/wined3d/drawprim.c @@ -682,7 +682,7 @@ static void drawStridedSlow(IWineD3DDevice *iface, WineDirect3DVertexStridedData specularColor = ptrToCoords[0]; VTRACE(("specularColor=%lx\n", specularColor)); - /* special case where the fog density is stored in the diffuse alpha channel */ + /* special case where the fog density is stored in the specular alpha channel */ if(This->stateBlock->renderState[WINED3DRS_FOGENABLE] && (This->stateBlock->renderState[WINED3DRS_FOGVERTEXMODE] == WINED3DFOG_NONE || sd->u.s.position.dwType == WINED3DDECLTYPE_FLOAT4 )&& This->stateBlock->renderState[WINED3DRS_FOGTABLEMODE] == WINED3DFOG_NONE) { diff --git a/dlls/wined3d/pixelshader.c b/dlls/wined3d/pixelshader.c index e9a2bc5a922..b74734d4681 100644 --- a/dlls/wined3d/pixelshader.c +++ b/dlls/wined3d/pixelshader.c @@ -381,6 +381,21 @@ static inline VOID IWineD3DPixelShaderImpl_GenerateShader( else shader_addline(&buffer, "gl_FragColor = R0;\n"); } + + /* Pixel shader < 3.0 do not replace the fog stage. + * This implements linear fog computation and blending. + * TODO: non linear fog + * NOTE: gl_Fog.start and gl_Fog.end don't hold fog start s and end e but + * -1/(e-s) and e/(e-s) respectively. + */ + if(This->baseShader.hex_version < WINED3DPS_VERSION(3,0)) { + shader_addline(&buffer, "float Fog = clamp(gl_FogFragCoord * gl_Fog.start + gl_Fog.end, 0.0, 1.0);\n"); + if(GL_SUPPORT(ARB_DRAW_BUFFERS)) + shader_addline(&buffer, "gl_FragData[0].xyz = mix(gl_Fog.color.xyz, gl_FragData[0].xyz, Fog);\n"); + else + shader_addline(&buffer, "gl_FragColor.xyz = mix(gl_Fog.color.xyz, gl_FragColor.xyz, Fog);\n"); + } + shader_addline(&buffer, "}\n"); TRACE("Compiling shader object %u\n", shader_obj); @@ -407,11 +422,28 @@ static inline VOID IWineD3DPixelShaderImpl_GenerateShader( /* Base Declarations */ shader_generate_arb_declarations( (IWineD3DBaseShader*) This, reg_maps, &buffer, &GLINFO_LOCATION); + /* We need two variables for fog blending */ + shader_addline(&buffer, "TEMP TMP_FOG;\n"); + if (This->baseShader.hex_version >= WINED3DPS_VERSION(2,0)) { + shader_addline(&buffer, "TEMP TMP_COLOR;\n"); + } + /* Base Shader Body */ shader_generate_main( (IWineD3DBaseShader*) This, &buffer, reg_maps, pFunction); - if (This->baseShader.hex_version < WINED3DPS_VERSION(2,0)) - shader_addline(&buffer, "MOV result.color, R0;\n"); + /* calculate fog and blend it + * NOTE: state.fog.params.y and state.fog.params.z don't hold fog start s and end e but + * -1/(e-s) and e/(e-s) respectively. + */ + shader_addline(&buffer, "MAD_SAT TMP_FOG, fragment.fogcoord, state.fog.params.y, state.fog.params.z;\n"); + if (This->baseShader.hex_version < WINED3DPS_VERSION(2,0)) { + shader_addline(&buffer, "LRP result.color.rgb, TMP_FOG.x, R0, state.fog.color;\n"); + shader_addline(&buffer, "MOV result.color.a, R0.a;\n"); + } else { + shader_addline(&buffer, "LRP result.color.rgb, TMP_FOG.x, TMP_COLOR, state.fog.color;\n"); + shader_addline(&buffer, "MOV result.color.a, TMP_COLOR.a;\n"); + } + shader_addline(&buffer, "END\n"); /* TODO: change to resource.glObjectHandle or something like that */ diff --git a/dlls/wined3d/state.c b/dlls/wined3d/state.c index aa9d0ab8d09..bba49eea084 100644 --- a/dlls/wined3d/state.c +++ b/dlls/wined3d/state.c @@ -689,6 +689,8 @@ static void state_stencilwrite(DWORD state, IWineD3DStateBlockImpl *stateblock, static void state_fog(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3DContext *context) { /* TODO: Put this into the vertex type block once that is in the state table */ BOOL fogenable = stateblock->renderState[WINED3DRS_FOGENABLE]; + BOOL is_ps3 = use_ps(stateblock->wineD3DDevice) + && ((IWineD3DPixelShaderImpl *)stateblock->pixelShader)->baseShader.hex_version >= WINED3DPS_VERSION(3,0); float fogstart, fogend; union { @@ -700,6 +702,17 @@ static void state_fog(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3DCo /* No fog? Disable it, and we're done :-) */ glDisable(GL_FOG); checkGLcall("glDisable GL_FOG"); + if( use_ps(stateblock->wineD3DDevice) + && ((IWineD3DPixelShaderImpl *)stateblock->pixelShader)->baseShader.hex_version < WINED3DPS_VERSION(3,0) ) { + /* disable fog in the pixel shader + * NOTE: For pixel shader, GL_FOG_START and GL_FOG_END don't hold fog start s and end e but + * -1/(e-s) and e/(e-s) respectively. + */ + glFogf(GL_FOG_START, 0.0f); + checkGLcall("glFogf(GL_FOG_START, fogstart"); + glFogf(GL_FOG_END, 1.0f); + checkGLcall("glFogf(GL_FOG_END, fogend"); + } return; } @@ -726,18 +739,80 @@ static void state_fog(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3DCo * FOGTABLEMODE == NONE, FOGVERTEXMODE == NONE, untransformed: * Linear fog with start = 255.0, end = 0.0, input comes from the specular color * - * Vertex shaders work in a simmilar way, but need more testing + * + * Rules for vertex fog with shaders: + * + * When mixing fixed function functionality with the programmable pipeline, D3D expects + * the fog computation to happen during transformation while openGL expects it to happen + * during rasterization. Also, prior to pixel shader 3.0 D3D handles fog blending after + * the pixel shader while openGL always expects the pixel shader to handle the blending. + * To solve this problem, WineD3D does: + * 1) implement a linear fog equation and fog blending at the end of every pre 3.0 pixel + * shader, + * and 2) disables the fog computation (in either the fixed function or programmable + * rasterizer) if using a vertex program. + * + * + * If a fogtablemode and a fogvertexmode are specified, table fog is applied (with or + * without shaders). */ + + if( is_ps3 ) { + if( !use_vs(stateblock->wineD3DDevice) + && stateblock->renderState[WINED3DRS_FOGTABLEMODE] == WINED3DFOG_NONE ) { + FIXME("Implement vertex fog for pixel shader >= 3.0 and fixed function pipeline\n"); + } + } + if (use_vs(stateblock->wineD3DDevice) && ((IWineD3DVertexShaderImpl *)stateblock->vertexShader)->usesFog) { - glFogi(GL_FOG_MODE, GL_LINEAR); - checkGLcall("glFogi(GL_FOG_MODE, GL_LINEAR)"); - if (stateblock->renderState[WINED3DRS_FOGTABLEMODE] == WINED3DFOG_NONE) { + if( stateblock->renderState[WINED3DRS_FOGTABLEMODE] != WINED3DFOG_NONE ) { + if(!is_ps3) FIXME("Implement table fog for foggy vertex shader\n"); + /* Disable fog */ + fogenable = FALSE; + } else { + /* Set fog computation in the rasterizer to pass through the value (just blend it) */ + glFogi(GL_FOG_MODE, GL_LINEAR); + checkGLcall("glFogi(GL_FOG_MODE, GL_LINEAR)"); fogstart = 1.0; fogend = 0.0; } context->last_was_foggy_shader = TRUE; } + else if( use_ps(stateblock->wineD3DDevice) ) { + /* NOTE: For pixel shader, GL_FOG_START and GL_FOG_END don't hold fog start s and end e but + * -1/(e-s) and e/(e-s) respectively to simplify fog computation in the shader. + */ + WINED3DFOGMODE mode; + context->last_was_foggy_shader = FALSE; + + /* If both fogmodes are set use the table fog mode */ + if(stateblock->renderState[WINED3DRS_FOGTABLEMODE] == WINED3DFOG_NONE) + mode = stateblock->renderState[WINED3DRS_FOGVERTEXMODE]; + else + mode = stateblock->renderState[WINED3DRS_FOGTABLEMODE]; + + switch (mode) { + case WINED3DFOG_EXP: + case WINED3DFOG_EXP2: + if(!is_ps3) FIXME("Implement non linear fog for pixel shader < 3.0\n"); + /* Disable fog */ + fogenable = FALSE; + break; + + case WINED3DFOG_LINEAR: + fogstart = -1.0f/(fogend-fogstart); + fogend *= -fogstart; + break; + + case WINED3DFOG_NONE: + if(!is_ps3) FIXME("Implement software vertex fog for pixel shader < 3.0\n"); + /* Disable fog */ + fogenable = FALSE; + break; + default: FIXME("Unexpected WINED3DRS_FOGVERTEXMODE %d\n", stateblock->renderState[WINED3DRS_FOGVERTEXMODE]); + } + } /* DX 7 sdk: "If both render states(vertex and table fog) are set to valid modes, * the system will apply only pixel(=table) fog effects." */ @@ -854,6 +929,16 @@ static void state_fog(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3DCo } else { glDisable(GL_FOG); checkGLcall("glDisable GL_FOG"); + if( use_ps(stateblock->wineD3DDevice) ) { + /* disable fog in the pixel shader + * NOTE: For pixel shader, GL_FOG_START and GL_FOG_END don't hold fog start s and end e but + * -1/(e-s) and e/(e-s) respectively. + */ + glFogf(GL_FOG_START, 0.0f); + checkGLcall("glFogf(GL_FOG_START, fogstart"); + glFogf(GL_FOG_END, 1.0f); + checkGLcall("glFogf(GL_FOG_END, fogend"); + } } if (GL_SUPPORT(NV_FOG_DISTANCE)) { @@ -864,7 +949,6 @@ static void state_fog(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3DCo static void state_fogcolor(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3DContext *context) { float col[4]; D3DCOLORTOGLFLOAT4(stateblock->renderState[WINED3DRS_FOGCOLOR], col); - /* Set the default alpha blend color */ glFogfv(GL_FOG_COLOR, &col[0]); checkGLcall("glFog GL_FOG_COLOR"); } @@ -2001,6 +2085,7 @@ static void pixelshader(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3D IWineD3DDeviceImpl *device = stateblock->wineD3DDevice; BOOL use_pshader = use_ps(device); BOOL use_vshader = use_vs(device); + BOOL update_fog = FALSE; int i; if (use_pshader) { @@ -2014,6 +2099,7 @@ static void pixelshader(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3D sampler(STATE_SAMPLER(i), stateblock, context); } } + update_fog = TRUE; } else { /* Otherwise all samplers were activated by the code above in earlier draws, or by sampler() * if a different texture was bound. I don't have to do anything. @@ -2031,6 +2117,8 @@ static void pixelshader(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3D tex_colorop(STATE_TEXTURESTAGE(i, WINED3DTSS_COLOROP), stateblock, context); } } + if(context->last_was_pshader) + update_fog = TRUE; } if(!isStateDirty(context, StateTable[STATE_VSHADER].representative)) { @@ -2041,6 +2129,9 @@ static void pixelshader(DWORD state, IWineD3DStateBlockImpl *stateblock, WineD3D } } + if(update_fog) + state_fog(state, stateblock, context); + context->last_was_pshader = use_pshader; }