bjarnia

Members
  • Content count

    136
  • Joined

  • Last visited

Community Reputation

219 Neutral

About bjarnia

  • Rank
    Member
  1. I'm trying to implement post processing in compute shaders and I've been having difficulty figuring out how I can read and write to the same UAV bound texture resource whose format is   DXGI_FORMAT_R16G16B16A16_FLOAT. An RGBA8 format would not be an issue as far as I can tell, nor would it be an issue if I was reading from a different intput buffer.. but those are unfortunately 2 constraints that I have :)   I can't simply type it as RWTexture2D<float4>, this forbids me from reading from it. I can do RWTexture2D<float>, but I have not found a way of retrieving and writing to all 4 float values of a single pixel.    I figured I'd have to use byte address buffers, but found this note on MSDN; "The UAV format bound to this resource needs to be created with the DXGI_FORMAT_R32_TYPELESS format.", but I need to operate on 64 bit granularity (so I only write to each "pixel" once.   Are there any good ways to do this?     
  2. Shadow maps being cut off

    Quote:Original post by szecs Here: *** Source Snippet Removed ***How do you calculate vTexCoord.z? (post code) Do you have all the projection matrices for each split? You have to calculate vTexCoord.z based on the proper projection matrix. And if you use DepthBias[split] here, your returned matrix should be like this:*** Source Snippet Removed *** But what do you use this returned matrix for? I think you should post your whole code ( between [source] and [/source] ) Alright! This is the shader (it actually creates a screen space shadow map that I then sample in my other shaders, but that should not be important). // Note that these values can be set by the application, although everything here looks ok except for SplitDistances, which is indeed set. static const int Splits = 4; int ShadowMapSize = 1024; float TexelSize = 1.0f / 1024; float DepthBias[] = {0.005f, 0.005f, 0.005f, 0.005}; float SplitDistances[Splits] = { 1, 1, 1, 1 }; texture ShadowMap0; texture ShadowMap1; texture ShadowMap2; texture ShadowMap3; float4x4 LightViewProjection[Splits]; sampler ShadowMapSampler[Splits] = { sampler_state { Texture = <ShadowMap0>; MinFilter = POINT; MagFilter = POINT; MipFilter = NONE; AddressU = BORDER; AddressV = BORDER; BorderColor = 0xFFFFFFFF; }, sampler_state { Texture = <ShadowMap1>; MinFilter = POINT; MagFilter = POINT; MipFilter = NONE; AddressU = BORDER; AddressV = BORDER; BorderColor = 0xFFFFFFFF; }, sampler_state { Texture = <ShadowMap2>; MinFilter = POINT; MagFilter = POINT; MipFilter = NONE; AddressU = BORDER; AddressV = BORDER; BorderColor = 0xFFFFFFFF; }, sampler_state { Texture = <ShadowMap3>; MinFilter = POINT; MagFilter = POINT; MipFilter = NONE; AddressU = BORDER; AddressV = BORDER; BorderColor = 0xFFFFFFFF; } }; float SamplePCF(sampler2D sam, float4 vTexCoord, int split) { float ret = 0; //vTexCoord.z = saturate(vTexCoord.z); /*if (vTexCoord.z >= 0.999) { ret = 1; } else*/ { // 2x2 PCF Filtering float fShadow[4]; fShadow[0] = (vTexCoord.z - DepthBias[split] < tex2D(sam, vTexCoord + float2(0, 0)).r); fShadow[1] = (vTexCoord.z - DepthBias[split] < tex2D(sam, vTexCoord + float2(TexelSize, 0)).r); fShadow[2] = (vTexCoord.z - DepthBias[split] < tex2D(sam, vTexCoord + float2(0, TexelSize)).r); fShadow[3] = (vTexCoord.z - DepthBias[split] < tex2D(sam, vTexCoord + float2(TexelSize, TexelSize)).r); float2 vLerpFactor = frac(ShadowMapSize * vTexCoord.xy); ret = lerp(lerp(fShadow[0], fShadow[1], vLerpFactor.x), lerp(fShadow[2], fShadow[3], vLerpFactor.x), vLerpFactor.y); } return ret; } float Sample(sampler2D sam, float4 vTexCoord, int split) { return (vTexCoord.z + DepthBias[split] < tex2D(sam, vTexCoord).r); } struct VS_RenderShadowsAndDepth_In { float4 Position : POSITION0; }; struct VS_RenderShadowsAndDepth_Out { float4 Position : POSITION0; float Depth : TEXCOORD0; float4 WorldPos : TEXCOORD1; float4 vTexCoord[Splits] : TEXCOORD2; }; VS_RenderShadowsAndDepth_Out VS_RenderShadowsAndDepth(VS_RenderShadowsAndDepth_In input) { VS_RenderShadowsAndDepth_Out output; float4x4 WorldViewProj = mul(mul(World, View), Projection); // Transform the models verticies and normal output.Position = mul(input.Position, WorldViewProj); //output.Depth = output.Position.z / MAXDEPTH; float4 worldPos = mul(input.Position, World); output.WorldPos = worldPos; output.Depth = output.Position.z / MAXDEPTH; // Depth // store view space position output.WorldPos = mul(worldPos, View); // coordinates for shadow maps for(int i = 0; i < Splits; i++) { output.vTexCoord[i] = mul(worldPos, LightViewProjection[i]); } return output; } PS_Out PS_RenderShadowsAndDepth(VS_RenderShadowsAndDepth_Out input) : COLOR { PS_Out output; output.Color = 1; output.Depth = float4(input.Depth, 0, 0, 1); float fLightingFactor = 1; //float fDistance = length(input.WorldPos - CameraPosition.xyz); //float fDistance = input.WorldPos.z * -1 - 1; float fDistance = input.Depth * MAXDEPTH; /*for(int i = 0; i < Splits; i++) { input.vTexCoord[i] = mul(input.WorldPos, LightViewProjection[i]); }*/ if(fDistance < SplitDistances[0]) { fLightingFactor = SamplePCF(ShadowMapSampler[0], input.vTexCoord[0], 0); output.Color = float4(1, 0, 0, 1); } else if(fDistance < SplitDistances[1]) { fLightingFactor = SamplePCF(ShadowMapSampler[1], input.vTexCoord[1], 1); output.Color = float4(0, 1, 0, 1); } else if(fDistance < SplitDistances[2]) { fLightingFactor = SamplePCF(ShadowMapSampler[2], input.vTexCoord[2], 2); output.Color = float4(0, 0, 1, 1); } else if(fDistance < SplitDistances[3]) { fLightingFactor = SamplePCF(ShadowMapSampler[3], input.vTexCoord[3], 3); output.Color = float4(1, 0, 1, 1); } //output.Color = 1; //output.Color.rgb *= clamp(fLightingFactor, 0.6, 1); output.Color.rgb *= fLightingFactor * 0.5 + 0.5; output.Color.a = 1; return output; } But yes, I have separate LightVP for each split. They are all multiplied by the same scaling matrix for sampling (not for rendering the depth maps). I'm wondering if I should post some of the actual app code, but I think it would be kind of confusing and I would probably need to paste a lot for it to make any sense. Edit: Pseduocode coming... This is my loop: Gather all shadow casters For each Split: - create basic view and projection matrices from the light POV - calculate a bounding frustum based on the split distance range - create a new list of shadow casters that fit into the new bounding frustum - if possible, tighten the frustum (if it's not very dense) - calculate a crop matrix based on this frustum - multiply this crop matrix with the lightVP - set this matrix as the view projection in my depth shader - draw the previously determined casters into a depth map render target Set all the depth maps as input to the screen space rendering shader Set all LVP * TexBiasMatrix as input to the SS shader Draw all the shadow receivers For vertex in recievers: - Calculate all shadow map texture coords based on the world position For each pixel in receivers: - Find the distance from camera and choose the correct split and texture coordinates and sample the corresponding sampler. Write this sample to the output buffer. Now we have a screen space shadow map that we can sample in screen space in any other shader. If I had to make a wild guess on what was wrong based on what I just said I would probably take a stab at the cropping/fitting code. I do however have faith in that since all of the depth maps render correctly. The only wildcard is the texture bias scale matrix since it's really hard to visualize what's going on with that (any tips appreciated) Ps: I think it's time for some sleep for me.
  3. Shadow maps being cut off

    Quote:Original post by szecs If you take a look at my signature, there is CSM in that, and it's even fixed pipeline! So I think I know what I'm talking about. CSM is nothing special in terms of matrices, shadow map generation etc. You just generate more shadow maps, and sample from one of them according to the distance from the camera. That's all. Well, there is one more important (as in significant to this problem) thing that I do (maybe that is normally tied into PSSM).. I crop/tighten the frustum based on the objects being drawn. But really though, it should still just be an ortho matrix (as is evident when the objects are drawn, nothing super special there, just less width/height). Appreciate the help though, in case you are interested in the GPU Gems sample I uploaded it here: http://www.steik.org/dump/ShadowStuff.rar Ps: Sorry for not mentioning the frustum cropping/compression before, should have done that.
  4. Shadow maps being cut off

    Quote:Original post by szecs Er... I posted the matrix code, did you miss that? No, I saw that, thanks. I did however try that as I noted, with very weird results (almost no shadows showing up and the ones that did were wrong). Maybe there is something special with the CSSM implementation or ortho lights that is different. I'm going to try to find out.
  5. Shadow maps being cut off

    Quote:Original post by RobMaddison Also, once you've got it working, you're sampling your 4 splits in an if/elseif statement which, I'm sure you already know, will cause all 4 samples to be taken unless you're using dynamic branching. A slightly more efficient way would be to have your shadow maps in an atlas and calculate the texture coords without using any ifs. Something to think about anyway... Thanks for the heads up. I'm actually not sure if dynamic branching is turned on or not, haven't investigated it yet. I fixed the flipping of the light, that was due to some shoddy code porting on my part. It did not improve the results though (although I managed to get rid of a renderstate switch where I flip the cull mode). I'm almost certain that the TextureScaleBias matrix is my problem. There does however not seem to be a magical solution. With what I have tried so far (which includes the above suggestions) I just get results where the shadows don't really make any sense at all. I'll do more research into that. This is however the code that was being used in the demo that I ported the sample from, and it works perfectly there. But there might be some coordinate system difference or something (I'm using XNA, other demo is D3D9). Interestingly enough the function even says: // Calculate a matrix to transform points to shadow map texture coordinates // (this should be exactly like in your standard shadow map implementation) Hmmm!
  6. Shadow maps being cut off

    Quote:Original post by RobMaddison I'm confused by your shadow maps, are you sure they're not inverted? Assuming your sun position is supposed to be behind your viewer's right shoulder, shouldn't the 1st shadowmap have the front of the car on the left of the image rather than the right? Edit: plus I totally agree with szecs that getting shadow mapping working with simple geometry is far easier. I'd stick a few different sized cubes in instead of the lada and try again. Interesting, I had not noticed that before. Thanks! Ps. If I wouldn't have had the lada and had a cube instead, you would not have noticed this error :)
  7. Shadow maps being cut off

    Quote:Original post by szecs Then why don't I see it in the code? Your code doesn't switch between textures, or you haven't posted the relevant code. And the scene is just confusing. you should use simpler meshes for testing, cubes, for example. But the generated shadow maps seem fine. I don't know, I guess I miss something, sorry. I refrained from posting the entire code as that that would likely be in the range of some hundred lines and resulting in a big wall of text. The code I posted is just my SamplePCF function: float SamplePCF(sampler2D sam, float4 vTexCoord, int split) It is called as follows in the pixel shader: if(fDistance < SplitDistances[0]) { fLightingFactor = SamplePCF(ShadowMapSampler[0], input.vTexCoord[0], 0); output.Color = float4(1, 0, 0, 1); } else if(fDistance < SplitDistances[1]) { fLightingFactor = SamplePCF(ShadowMapSampler[1], input.vTexCoord[1], 1); output.Color = float4(0, 1, 0, 1); } else if(fDistance < SplitDistances[2]) { fLightingFactor = SamplePCF(ShadowMapSampler[2], input.vTexCoord[2], 2); output.Color = float4(0, 0, 1, 1); } else if(fDistance < SplitDistances[3]) { fLightingFactor = SamplePCF(ShadowMapSampler[3], input.vTexCoord[3], 3); output.Color = float4(1, 0, 1, 1); } the output.Color assignments are for the debugging of split positions. As for the scene setup, I would normally agree, but with real objects I feel like I have a better view of what the shadow should actually be. But maybe I'm just weird :) Quote: BTW, your returned matrix seems very strange. You only have to apply the offset (bias) in the 'z' direction: (0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, shadowMapTexOffset, 1.0f); //or maybe -shadowMapTexOffset Or I totally misunderstand something. Hmm.. thanks, I'll take a look.
  8. Shadow maps being cut off

    Quote:Original post by szecs Hey, nice Lada 1200 model! My guess is that you only apply the first (closest-to-light) shadowmap to the scene. (BTW how did you get the working version?) You have to sample from the other maps too. I'm not in the shader stuff yet, and I don't know how the CSM works in that case, but I have a hunch that you should do something like this: texture_unit = floor(vTexCoord.z); // will be 2 if vTexCoord.z = 2.3 vTexCoord.z -= texture_unit; // will be 0.3 if vTexCoord.z = 2.3 .... //take sample from texture_unitBut it's just a wild guess. Working version of the GPU Gems Code? I can email it to you if you want. I'm sampling from all of the maps, based on the coloring you see in the screenshot, thought that was kind of a given :) Red is closest, then green, blue and magenta. That part works. Edit: Also, that explanation would not fit since the part that is casting shadow is actually rendered on all of the depth maps.
  9. I'm trying to get my cascaded shadow maps implementation working and I'm facing a problem that I'm stuck on. This is the intended shadow for the car: http://www.steik.org/dump/Cosmopolis2010-04-1100-20-55-11.png This is however what I will see with a slightly different item-to-split population: http://www.steik.org/dump/Cosmopolis2010-04-1100-21-28-51.png During my debugging I came up with a dirty hack in the PCF function to at least make the wrong areas not black (shadowed) (see the first if statement) if (vTexCoord.z >= 0.999) { ret = 1; } else { // 2x2 PCF Filtering float fShadow[4]; fShadow[0] = (vTexCoord.z - DepthBias[split] < tex2D(sam, vTexCoord + float2(0, 0)).r); fShadow[1] = (vTexCoord.z - DepthBias[split] < tex2D(sam, vTexCoord + float2(TexelSize, 0)).r); fShadow[2] = (vTexCoord.z - DepthBias[split] < tex2D(sam, vTexCoord + float2(0, TexelSize)).r); fShadow[3] = (vTexCoord.z - DepthBias[split] < tex2D(sam, vTexCoord + float2(TexelSize, TexelSize)).r); float2 vLerpFactor = frac(ShadowMapSize * vTexCoord.xy); ret = lerp(lerp(fShadow[0], fShadow[1], vLerpFactor.x), lerp(fShadow[2], fShadow[3], vLerpFactor.x), vLerpFactor.y); } This is what happens with the hack: http://www.steik.org/dump/Cosmopolis2010-04-1100-20-51-76.png As you can see this is also a problem with the first split in all pictures. In all cases the geometry IS clearly being written in the depth map (see frames 1 and 2 on the top from the left), so I think it is just some stupid texture coordinate problem. I however have no idea pretty much why or how to fix this. I am basing my code on "Chapter 10: Parallel-Split Shadow Maps on Programmable GPUs" from GPU Gems 3. Clearly the LightViewProjection is correct since the depth maps are being drawn correctly, and the only thing I do extra when I sample it later is this: LightViewProjectionCropped * shadowMapTextureScale where the scaling matrix is a relatively standard matrix: float shadowMapTexOffset = 0.5f + (0.5f / (float)shadowMapSize); return new Matrix(0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, shadowMapTexOffset, shadowMapTexOffset, 0.0f, 1.0f); So yeah.. This error doesn't really make sense to me since the depth maps are being drawn right. Any ideas?
  10. I'm trying to embed roads into my terrain, and it works fine for tiling (all edges) road textures, but what I'm more interested in is getting it to work with road textures that actually look like roads such as this: http://www.steik.org/dump/road.png This is what I have right now: http://steik.org/dump/Cosmopolis2010-03-1711-24-46-57.png The white line is a curve (CatmullRom, but can be whatever) that "imprints" itself into the terrain on terrain creation time, adding vertex data to the terrain. The vertex data is the distance from the center of the curve (for fading out edges) and the direction (atan2 of the normalized direction vector). I know how to do basic rotation of texture coordinates, but well.. the results are nothing usable: http://www.steik.org/dump/Cosmopolis2010-03-1711-37-39-81.png I have tried adding a 90 or 270° rotation to it, but it did not do much. This is how I do the rotation at the moment (Pixel Shader): float2 texCoord = input.WorldPos.xz / 6; texCoord.x -= (int)texCoord.x; texCoord.y -= (int)texCoord.y; float theta = input.Road.y; // atan2 of segment direction float s = sin(theta); float c = cos(theta); texCoord = mul(texCoord, float2x2(c, -s, s, c)); So... I guess the main question is.. Does this approach even make sense? Right now I think not :) Should I try to compute the texture coordinates as I create the road somehow instead of doing it in PS based on the direction? Any other ideas?
  11. I have stumbled upon references to the use of "Receiver Plane Depth Bias" to alleviate errors in traditional shadow maps. Such as here (page 37): http://ati.amd.com/developer/gdc/2006/Isidoro-ShadowMapping.pdf As well as realizing that there is a DX renderstate that seems to set "SlopeScaleDepthBias" which to my understanding is the same thing. I have tried to integrate the implementation in the above presentation with no luck at all, as well as trying a lot of different values for that renderstate with little effect. Can someone point me to a simple sample or a paper that uses this technique properly? Maybe my googling skills are just wearing thin :P I am using an orthographic light if that makes a difference. This is how my very naive implementation looks: http://www.steik.org/dump/Cosmopolis2010-02-1718-56-36-56.png I'll just include my HLSL code too... What is confusing me the most is if I can just use my normal shadow texture coordinates? I'm thinking not? I am anyway :) The code has been modified slightly to account for lack of Fetch4 that the presentation assumes. float2 quadOffsets[] = { float2( 0, 0), float2( 1, 0), float2( 1, 1), float2( 1, 0), float2( 1, -1), float2( 0, -1), float2(-1, -1), float2(-1, 0), float2(-1, 1)}; float SamplePCFSlope(sampler2D depthSampler, float4 texCoord, float bias, float texelSize, float mapSize) { //Packing derivatives of u,v, and distance to light source w.r.t. screen space x, and y float4 duvdist_dx = ddx(texCoord); float4 duvdist_dy = ddy(texCoord); //Invert texture Jacobian and use chain rule to compute ddist/du and ddist/dv // |ddist/du| = |du/dx du/dy|-T * |ddist/dx| // |ddist/dv| |dv/dx dv/dy| |ddist/dy| //Multiply ddist/dx and ddist/dy by inverse transpose of Jacobian float invDet = 1 / ((duvdist_dx.x * duvdist_dy.y) - (duvdist_dx.y * duvdist_dy.x) ); //Top row of 2x2 float2 ddist_duv; ddist_duv.x = duvdist_dy.y * duvdist_dx.w ; // invJtrans[0][0] * ddist_dx ddist_duv.x -= duvdist_dx.y * duvdist_dy.w ; // invJtrans[0][1] * ddist_dy //Bottom row of 2x2 ddist_duv.y = duvdist_dx.x * duvdist_dy.w ; // invJtrans[1][1] * ddist_dy ddist_duv.y -= duvdist_dy.x * duvdist_dx.w ; // invJtrans[1][0] * ddist_dx ddist_duv *= invDet; float percentInLight = 0; float2 texCoordOffset; float shadowMapVals; float dist; float4 inLight; float invNumTaps = 1.0 / 9.0f; // Not ideal, too lazy to set in code for now float2 g_vFullTexelOffset = float2(texelSize, texelSize); //compute depth offset and PCF taps 4 at a time for(int i=0; i < 9; i++) { //offset of texel quad in texture coordinates; texCoordOffset = (g_vFullTexelOffset * quadOffsets[i] ); //shadow map values shadowMapVals = tex2D(depthSampler, texCoord.xy + texCoordOffset.xy ).r; //Apply receiver plane depth offset dist = texCoord.w + (ddist_duv.x * texCoordOffset.x) + (ddist_duv.y * texCoordOffset.y); //dist = texCoord.z + bias; // Normal shadow mapping.. Works fine inLight = ( dist < shadowMapVals ); percentInLight += inLight * invNumTaps; } return percentInLight; }
  12. Try to play with the FOV as well.
  13. Well your post made me want to do a better benchmark where the random calls and the typecast overheads were avoided. All I can say is OUCH because the generics code got dismembered. Time: 00:00:00.6260369 (Simple) Time: 00:00:04.4302525 (Generics) Same functions as above, diff main Edit: I noticed I got some massive negative rep from someone... lol, so much for trying to help. static void Main(string[] args) { Random r = new Random(); float min, max; TimeSpan t1 = new TimeSpan(); TimeSpan t2 = new TimeSpan(); for (int j = 0; j &lt; 50000; j++) { float f1 = (float)r.NextDouble(); float f2 = (float)r.NextDouble(); float f3 = (float)r.NextDouble(); DateTime start1 = DateTime.Now; for (int i = 0; i &lt; 500; i++) { MinMax2(f1, f2, f3, out min, out max); MinMax2(f1, f3, f2, out min, out max); MinMax2(f2, f1, f3, out min, out max); MinMax2(f2, f3, f1, out min, out max); MinMax2(f3, f2, f1, out min, out max); MinMax2(f3, f1, f2, out min, out max); } t1 += DateTime.Now - start1; DateTime start2 = DateTime.Now; for (int i = 0; i &lt; 500; i++) { MinMax(out min, out max, f1, f2, f3); MinMax(out min, out max, f1, f3, f2); MinMax(out min, out max, f2, f1, f3); MinMax(out min, out max, f2, f3, f1); MinMax(out min, out max, f3, f2, f1); MinMax(out min, out max, f3, f1, f2); } t2 += DateTime.Now - start2; } Console.WriteLine("Time: {0}", t1); Console.WriteLine("Time: {0}", t2); Console.ReadLine(); }
  14. Quote: Quote:It's my instinct that the simpler function would be faster than your version. Wrong. Consider those C functions, which compute the remainder of a division of a number x by 3, under the assumption that x is in the interval [0..2]: Wrong? What was wrong? My instincts? (clearly not, after I benchmarked it was confirmed) Not going to bother with responding to the rest of the stuff since you are coming off extremely defensive as if I challenged your manhood. And here is the benchmark code. Actually posted it yesterday but didn't know what tag to use so it would become scrollable so I didn't feel like it was worth ruining the thread as it's just a repitition of the above code. And yes optimization is turned on. Edit: Found out about the source tag using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Random r = new Random(); float min, max; { DateTime start = DateTime.Now; for (int i = 0; i &lt; 50000000; i++) MinMax2((float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), out min, out max); Console.WriteLine("Time: {0}", start - DateTime.Now); } { DateTime start = DateTime.Now; for (int i = 0; i &lt; 50000000; i++) MinMax(out min, out max, (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble()); Console.WriteLine("Time: {0}", start - DateTime.Now); } Console.ReadLine(); } static void MinMax2(float x0, float x1, float x2, out float min, out float max) { min = max = x0; if (x1 &lt; min) min = x1; if (x1 &gt; max) max = x1; if (x2 &lt; min) min = x2; if (x2 &gt; max) max = x2; } // At first, the general form, which works for every parameter count // n &gt;= 1 (which I consider important, because the minimum and maximum // of a set that only contains 1 element is very well that element, // and absolutely not undefined) static T Max&lt;T&gt;(T arg0, params T[] ps) where T : IComparable&lt;T&gt; { T max = arg0; foreach (T curr in ps) max = Max(max, curr); return max; } static T Min&lt;T&gt;(T arg0, params T[] ps) where T : IComparable&lt;T&gt; { T min = arg0; foreach (T curr in ps) min = curr.CompareTo(min) &lt; 0 ? curr : min; return min; } // 1 static T Max&lt;T&gt;(T arg0) where T : IComparable&lt;T&gt; { return arg0; } static T Min&lt;T&gt;(T arg0) where T : IComparable&lt;T&gt; { return arg0; } static void MinMax&lt;T&gt;(out T min, out T max, T arg0) where T : IComparable&lt;T&gt; { min = max = arg0; } // 2 static T Max&lt;T&gt;(T arg0, T arg1) where T : IComparable&lt;T&gt; { return arg0.CompareTo(arg1) &gt; 0 ? arg0 : arg1; } static T Min&lt;T&gt;(T arg0, T arg1) where T : IComparable&lt;T&gt; { return arg0.CompareTo(arg1) &lt; 0 ? arg0 : arg1; } static void MinMax&lt;T&gt;(out T min, out T max, T arg0, T arg1) where T : IComparable&lt;T&gt; { // could be made branch free if (arg0.CompareTo(arg1) &lt; 0) { min = arg0; max = arg1; } else { min = arg1; max = arg0; } } // 3 static T Max&lt;T&gt;(T arg0, T arg1, T arg2) where T : IComparable&lt;T&gt; { return Max(Max(arg0, arg1), arg2); } static T Min&lt;T&gt;(T arg0, T arg1, T arg2) where T : IComparable&lt;T&gt; { return Min(Min(arg0, arg1), arg2); } static void MinMax&lt;T&gt;(out T min, out T max, T arg0, T arg1, T arg2) where T : IComparable&lt;T&gt; { min = Min(arg0, arg1, arg2); max = Max(arg0, arg1, arg2); } // 4 static T Max&lt;T&gt;(T arg0, T arg1, T arg2, T arg3) where T : IComparable&lt;T&gt; { return Max(Max(arg0, arg1), Max(arg2, arg3)); } static T Min&lt;T&gt;(T arg0, T arg1, T arg2, T arg3) where T : IComparable&lt;T&gt; { return Min(Min(arg0, arg1), Min(arg2, arg3)); } static void MinMax&lt;T&gt;(out T min, out T max, T arg0, T arg1, T arg2, T arg3) where T : IComparable&lt;T&gt; { min = Min(arg0, arg1, arg2, arg3); max = Max(arg0, arg1, arg2, arg3); } } } [Edited by - bjarnia on May 11, 2009 11:59:21 AM]
  15. I just performed some informal testing... Over 10000000 triangles created with Random, the generic function was twice as slow as the simple one. 1.5 seconds vs 0.75 seconds. Edit: that was on debug... The difference is not as big in release but there is still a noticable difference, especially for a function that is going to be called so often. Release compiled without debugger attached: (50000000 triangles Time: 00:00:02.4691412 (Simple) Time: 00:00:03.8562034 (Generics)