/* ======================================================================== $File: $ $Date: $ $Revision: $ $Creator: Casey Muratori $ $Notice: (C) Copyright 2015 by Molly Rocket, Inc. All Rights Reserved. $ ======================================================================== */ #define IGNORED_TIMED_FUNCTION TIMED_FUNCTION inline v4 Unpack4x8(uint32 Packed) { v4 Result = {(real32)((Packed >> 16) & 0xFF), (real32)((Packed >> 8) & 0xFF), (real32)((Packed >> 0) & 0xFF), (real32)((Packed >> 24) & 0xFF)}; return(Result); } inline v4 UnscaleAndBiasNormal(v4 Normal) { v4 Result; real32 Inv255 = 1.0f / 255.0f; Result.x = -1.0f + 2.0f*(Inv255*Normal.x); Result.y = -1.0f + 2.0f*(Inv255*Normal.y); Result.z = -1.0f + 2.0f*(Inv255*Normal.z); Result.w = Inv255*Normal.w; return(Result); } internal void DrawRectangle(loaded_bitmap *Buffer, v2 vMin, v2 vMax, v4 Color, rectangle2i ClipRect, bool32 Even) { TIMED_FUNCTION(); real32 R = Color.r; real32 G = Color.g; real32 B = Color.b; real32 A = Color.a; rectangle2i FillRect; FillRect.MinX = RoundReal32ToInt32(vMin.x); FillRect.MinY = RoundReal32ToInt32(vMin.y); FillRect.MaxX = RoundReal32ToInt32(vMax.x); FillRect.MaxY = RoundReal32ToInt32(vMax.y); FillRect = Intersect(FillRect, ClipRect); if(!Even == (FillRect.MinY & 1)) { FillRect.MinY += 1; } uint32 Color32 = ((RoundReal32ToUInt32(A * 255.0f) << 24) | (RoundReal32ToUInt32(R * 255.0f) << 16) | (RoundReal32ToUInt32(G * 255.0f) << 8) | (RoundReal32ToUInt32(B * 255.0f) << 0)); uint8 *Row = ((uint8 *)Buffer->Memory + FillRect.MinX*BITMAP_BYTES_PER_PIXEL + FillRect.MinY*Buffer->Pitch); for(int Y = FillRect.MinY; Y < FillRect.MaxY; Y += 2) { uint32 *Pixel = (uint32 *)Row; for(int X = FillRect.MinX; X < FillRect.MaxX; ++X) { *Pixel++ = Color32; } Row += 2*Buffer->Pitch; } } struct bilinear_sample { uint32 A, B, C, D; }; inline bilinear_sample BilinearSample(loaded_bitmap *Texture, int32 X, int32 Y) { bilinear_sample Result; uint8 *TexelPtr = ((uint8 *)Texture->Memory) + Y*Texture->Pitch + X*sizeof(uint32); Result.A = *(uint32 *)(TexelPtr); Result.B = *(uint32 *)(TexelPtr + sizeof(uint32)); Result.C = *(uint32 *)(TexelPtr + Texture->Pitch); Result.D = *(uint32 *)(TexelPtr + Texture->Pitch + sizeof(uint32)); return(Result); } inline v4 SRGBBilinearBlend(bilinear_sample TexelSample, real32 fX, real32 fY) { v4 TexelA = Unpack4x8(TexelSample.A); v4 TexelB = Unpack4x8(TexelSample.B); v4 TexelC = Unpack4x8(TexelSample.C); v4 TexelD = Unpack4x8(TexelSample.D); // NOTE(casey): Go from sRGB to "linear" brightness space TexelA = SRGB255ToLinear1(TexelA); TexelB = SRGB255ToLinear1(TexelB); TexelC = SRGB255ToLinear1(TexelC); TexelD = SRGB255ToLinear1(TexelD); v4 Result = Lerp(Lerp(TexelA, fX, TexelB), fY, Lerp(TexelC, fX, TexelD)); return(Result); } inline v3 SampleEnvironmentMap(v2 ScreenSpaceUV, v3 SampleDirection, real32 Roughness, environment_map *Map, real32 DistanceFromMapInZ) { /* NOTE(casey): ScreenSpaceUV tells us where the ray is being cast _from_ in normalized screen coordinates. SampleDirection tells us what direction the cast is going - it does not have to be normalized. Roughness says which LODs of Map we sample from. DistanceFromMapInZ says how far the map is from the sample point in Z, given in meters. */ // NOTE(casey): Pick which LOD to sample from uint32 LODIndex = (uint32)(Roughness*(real32)(ArrayCount(Map->LOD) - 1) + 0.5f); Assert(LODIndex < ArrayCount(Map->LOD)); loaded_bitmap *LOD = &Map->LOD[LODIndex]; // NOTE(casey): Compute the distance to the map and the scaling // factor for meters-to-UVs real32 UVsPerMeter = 0.1f; // TODO(casey): Parameterize this, and should be different for X and Y based on map! real32 C = (UVsPerMeter*DistanceFromMapInZ) / SampleDirection.y; v2 Offset = C * V2(SampleDirection.x, SampleDirection.z); // NOTE(casey): Find the intersection point v2 UV = ScreenSpaceUV + Offset; // NOTE(casey): Clamp to the valid range UV.x = Clamp01(UV.x); UV.y = Clamp01(UV.y); // NOTE(casey): Bilinear sample // TODO(casey): Formalize texture boundaries!!! real32 tX = ((UV.x*(real32)(LOD->Width - 2))); real32 tY = ((UV.y*(real32)(LOD->Height - 2))); int32 X = (int32)tX; int32 Y = (int32)tY; real32 fX = tX - (real32)X; real32 fY = tY - (real32)Y; Assert((X >= 0) && (X < LOD->Width)); Assert((Y >= 0) && (Y < LOD->Height)); DEBUG_IF(Renderer_ShowLightingSamples) { // NOTE(casey): Turn this on to see where in the map you're sampling! uint8 *TexelPtr = ((uint8 *)LOD->Memory) + Y*LOD->Pitch + X*sizeof(uint32); *(uint32 *)TexelPtr = 0xFFFFFFFF; } bilinear_sample Sample = BilinearSample(LOD, X, Y); v3 Result = SRGBBilinearBlend(Sample, fX, fY).xyz; return(Result); } internal void DrawRectangleSlowly(loaded_bitmap *Buffer, v2 Origin, v2 XAxis, v2 YAxis, v4 Color, loaded_bitmap *Texture, loaded_bitmap *NormalMap, environment_map *Top, environment_map *Middle, environment_map *Bottom, real32 PixelsToMeters) { IGNORED_TIMED_FUNCTION(); // NOTE(casey): Premultiply color up front Color.rgb *= Color.a; real32 XAxisLength = Length(XAxis); real32 YAxisLength = Length(YAxis); v2 NxAxis = (YAxisLength / XAxisLength) * XAxis; v2 NyAxis = (XAxisLength / YAxisLength) * YAxis; // NOTE(casey): NzScale could be a parameter if we want people to // have control over the amount of scaling in the Z direction // that the normals appear to have. real32 NzScale = 0.5f*(XAxisLength + YAxisLength); real32 InvXAxisLengthSq = 1.0f / LengthSq(XAxis); real32 InvYAxisLengthSq = 1.0f / LengthSq(YAxis); uint32 Color32 = ((RoundReal32ToUInt32(Color.a * 255.0f) << 24) | (RoundReal32ToUInt32(Color.r * 255.0f) << 16) | (RoundReal32ToUInt32(Color.g * 255.0f) << 8) | (RoundReal32ToUInt32(Color.b * 255.0f) << 0)); int WidthMax = (Buffer->Width - 1); int HeightMax = (Buffer->Height - 1); real32 InvWidthMax = 1.0f / (real32)WidthMax; real32 InvHeightMax = 1.0f / (real32)HeightMax; // TODO(casey): This will need to be specified separately!!! real32 OriginZ = 0.0f; real32 OriginY = (Origin + 0.5f*XAxis + 0.5f*YAxis).y; real32 FixedCastY = InvHeightMax*OriginY; int XMin = WidthMax; int XMax = 0; int YMin = HeightMax; int YMax = 0; v2 P[4] = {Origin, Origin + XAxis, Origin + XAxis + YAxis, Origin + YAxis}; for(int PIndex = 0; PIndex < ArrayCount(P); ++PIndex) { v2 TestP = P[PIndex]; int FloorX = FloorReal32ToInt32(TestP.x); int CeilX = CeilReal32ToInt32(TestP.x); int FloorY = FloorReal32ToInt32(TestP.y); int CeilY = CeilReal32ToInt32(TestP.y); if(XMin > FloorX) {XMin = FloorX;} if(YMin > FloorY) {YMin = FloorY;} if(XMax < CeilX) {XMax = CeilX;} if(YMax < CeilY) {YMax = CeilY;} } if(XMin < 0) {XMin = 0;} if(YMin < 0) {YMin = 0;} if(XMax > WidthMax) {XMax = WidthMax;} if(YMax > HeightMax) {YMax = HeightMax;} uint8 *Row = ((uint8 *)Buffer->Memory + XMin*BITMAP_BYTES_PER_PIXEL + YMin*Buffer->Pitch); TIMED_BLOCK(PixelFill, (XMax - XMin + 1)*(YMax - YMin + 1)); for(int Y = YMin; Y <= YMax; ++Y) { uint32 *Pixel = (uint32 *)Row; for(int X = XMin; X <= XMax; ++X) { #if 1 v2 PixelP = V2i(X, Y); v2 d = PixelP - Origin; // TODO(casey): PerpInner // TODO(casey): Simpler origin real32 Edge0 = Inner(d, -Perp(XAxis)); real32 Edge1 = Inner(d - XAxis, -Perp(YAxis)); real32 Edge2 = Inner(d - XAxis - YAxis, Perp(XAxis)); real32 Edge3 = Inner(d - YAxis, Perp(YAxis)); if((Edge0 < 0) && (Edge1 < 0) && (Edge2 < 0) && (Edge3 < 0)) { #if 1 v2 ScreenSpaceUV = {InvWidthMax*(real32)X, FixedCastY}; real32 ZDiff = PixelsToMeters*((real32)Y - OriginY); #else v2 ScreenSpaceUV = {InvWidthMax*(real32)X, InvHeightMax*(real32)Y}; real32 ZDiff = 0.0f; #endif real32 U = InvXAxisLengthSq*Inner(d, XAxis); real32 V = InvYAxisLengthSq*Inner(d, YAxis); #if 0 // TODO(casey): SSE clamping. Assert((U >= 0.0f) && (U <= 1.0f)); Assert((V >= 0.0f) && (V <= 1.0f)); #endif // TODO(casey): Formalize texture boundaries!!! real32 tX = ((U*(real32)(Texture->Width - 2))); real32 tY = ((V*(real32)(Texture->Height - 2))); int32 X = (int32)tX; int32 Y = (int32)tY; real32 fX = tX - (real32)X; real32 fY = tY - (real32)Y; Assert((X >= 0) && (X < Texture->Width)); Assert((Y >= 0) && (Y < Texture->Height)); bilinear_sample TexelSample = BilinearSample(Texture, X, Y); v4 Texel = SRGBBilinearBlend(TexelSample, fX, fY); #if 0 if(NormalMap) { bilinear_sample NormalSample = BilinearSample(NormalMap, X, Y); v4 NormalA = Unpack4x8(NormalSample.A); v4 NormalB = Unpack4x8(NormalSample.B); v4 NormalC = Unpack4x8(NormalSample.C); v4 NormalD = Unpack4x8(NormalSample.D); v4 Normal = Lerp(Lerp(NormalA, fX, NormalB), fY, Lerp(NormalC, fX, NormalD)); Normal = UnscaleAndBiasNormal(Normal); // TODO(casey): Do we really need to do this? Normal.xy = Normal.x*NxAxis + Normal.y*NyAxis; Normal.z *= NzScale; Normal.xyz = Normalize(Normal.xyz); // NOTE(casey): The eye vector is always assumed to be [0, 0, 1] // This is just the simplified version of the reflection -e + 2e^T N N v3 BounceDirection = 2.0f*Normal.z*Normal.xyz; BounceDirection.z -= 1.0f; // TODO(casey): Eventually we need to support two mappings, // one for top-down view (which we don't do now) and one // for sideways, which is what's happening here. BounceDirection.z = -BounceDirection.z; environment_map *FarMap = 0; real32 Pz = OriginZ + ZDiff; real32 MapZ = 2.0f; real32 tEnvMap = BounceDirection.y; real32 tFarMap = 0.0f; if(tEnvMap < -0.5f) { // TODO(casey): This path seems PARTICULARLY broken! FarMap = Bottom; tFarMap = -1.0f - 2.0f*tEnvMap; } else if(tEnvMap > 0.5f) { FarMap = Top; tFarMap = 2.0f*(tEnvMap - 0.5f); } tFarMap *= tFarMap; tFarMap *= tFarMap; v3 LightColor = {0, 0, 0}; // TODO(casey): How do we sample from the middle map??? if(FarMap) { real32 DistanceFromMapInZ = FarMap->Pz - Pz; v3 FarMapColor = SampleEnvironmentMap(ScreenSpaceUV, BounceDirection, Normal.w, FarMap, DistanceFromMapInZ); LightColor = Lerp(LightColor, tFarMap, FarMapColor); } // TODO(casey): ? Actually do a lighting model computation here Texel.rgb = Texel.rgb + Texel.a*LightColor; #if 0 // NOTE(casey): Draws the bounce direction Texel.rgb = V3(0.5f, 0.5f, 0.5f) + 0.5f*BounceDirection; Texel.rgb *= Texel.a; #endif } #endif Texel = Hadamard(Texel, Color); Texel.r = Clamp01(Texel.r); Texel.g = Clamp01(Texel.g); Texel.b = Clamp01(Texel.b); v4 Dest = {(real32)((*Pixel >> 16) & 0xFF), (real32)((*Pixel >> 8) & 0xFF), (real32)((*Pixel >> 0) & 0xFF), (real32)((*Pixel >> 24) & 0xFF)}; // NOTE(casey): Go from sRGB to "linear" brightness space Dest = SRGB255ToLinear1(Dest); v4 Blended = (1.0f-Texel.a)*Dest + Texel; // NOTE(casey): Go from "linear" brightness space to sRGB v4 Blended255 = Linear1ToSRGB255(Blended); *Pixel = (((uint32)(Blended255.a + 0.5f) << 24) | ((uint32)(Blended255.r + 0.5f) << 16) | ((uint32)(Blended255.g + 0.5f) << 8) | ((uint32)(Blended255.b + 0.5f) << 0)); } #else *Pixel = Color32; #endif ++Pixel; } Row += Buffer->Pitch; } } internal void DrawBitmap(loaded_bitmap *Buffer, loaded_bitmap *Bitmap, real32 RealX, real32 RealY, real32 CAlpha = 1.0f) { IGNORED_TIMED_FUNCTION(); int32 MinX = RoundReal32ToInt32(RealX); int32 MinY = RoundReal32ToInt32(RealY); int32 MaxX = MinX + Bitmap->Width; int32 MaxY = MinY + Bitmap->Height; int32 SourceOffsetX = 0; if(MinX < 0) { SourceOffsetX = -MinX; MinX = 0; } int32 SourceOffsetY = 0; if(MinY < 0) { SourceOffsetY = -MinY; MinY = 0; } if(MaxX > Buffer->Width) { MaxX = Buffer->Width; } if(MaxY > Buffer->Height) { MaxY = Buffer->Height; } uint8 *SourceRow = (uint8 *)Bitmap->Memory + SourceOffsetY*Bitmap->Pitch + BITMAP_BYTES_PER_PIXEL*SourceOffsetX; uint8 *DestRow = ((uint8 *)Buffer->Memory + MinX*BITMAP_BYTES_PER_PIXEL + MinY*Buffer->Pitch); for(int Y = MinY; Y < MaxY; ++Y) { uint32 *Dest = (uint32 *)DestRow; uint32 *Source = (uint32 *)SourceRow; for(int X = MinX; X < MaxX; ++X) { v4 Texel = {(real32)((*Source >> 16) & 0xFF), (real32)((*Source >> 8) & 0xFF), (real32)((*Source >> 0) & 0xFF), (real32)((*Source >> 24) & 0xFF)}; Texel = SRGB255ToLinear1(Texel); Texel *= CAlpha; v4 D = {(real32)((*Dest >> 16) & 0xFF), (real32)((*Dest >> 8) & 0xFF), (real32)((*Dest >> 0) & 0xFF), (real32)((*Dest >> 24) & 0xFF)}; D = SRGB255ToLinear1(D); v4 Result = (1.0f-Texel.a)*D + Texel; Result = Linear1ToSRGB255(Result); *Dest = (((uint32)(Result.a + 0.5f) << 24) | ((uint32)(Result.r + 0.5f) << 16) | ((uint32)(Result.g + 0.5f) << 8) | ((uint32)(Result.b + 0.5f) << 0)); ++Dest; ++Source; } DestRow += Buffer->Pitch; SourceRow += Bitmap->Pitch; } } internal void ChangeSaturation(loaded_bitmap *Buffer, real32 Level) { IGNORED_TIMED_FUNCTION(); uint8 *DestRow = (uint8 *)Buffer->Memory; for(int Y = 0; Y < Buffer->Height; ++Y) { uint32 *Dest = (uint32 *)DestRow; for(int X = 0; X < Buffer->Width; ++X) { v4 D = {(real32)((*Dest >> 16) & 0xFF), (real32)((*Dest >> 8) & 0xFF), (real32)((*Dest >> 0) & 0xFF), (real32)((*Dest >> 24) & 0xFF)}; D = SRGB255ToLinear1(D); real32 Avg = (1.0f / 3.0f) * (D.r + D.g + D.b); v3 Delta = V3(D.r - Avg, D.g - Avg, D.b - Avg); v4 Result = V4(V3(Avg, Avg, Avg) + Level*Delta, D.a); Result = Linear1ToSRGB255(Result); *Dest = (((uint32)(Result.a + 0.5f) << 24) | ((uint32)(Result.r + 0.5f) << 16) | ((uint32)(Result.g + 0.5f) << 8) | ((uint32)(Result.b + 0.5f) << 0)); ++Dest; } DestRow += Buffer->Pitch; } } internal void DrawMatte(loaded_bitmap *Buffer, loaded_bitmap *Bitmap, real32 RealX, real32 RealY, real32 CAlpha = 1.0f) { IGNORED_TIMED_FUNCTION(); int32 MinX = RoundReal32ToInt32(RealX); int32 MinY = RoundReal32ToInt32(RealY); int32 MaxX = MinX + Bitmap->Width; int32 MaxY = MinY + Bitmap->Height; int32 SourceOffsetX = 0; if(MinX < 0) { SourceOffsetX = -MinX; MinX = 0; } int32 SourceOffsetY = 0; if(MinY < 0) { SourceOffsetY = -MinY; MinY = 0; } if(MaxX > Buffer->Width) { MaxX = Buffer->Width; } if(MaxY > Buffer->Height) { MaxY = Buffer->Height; } uint8 *SourceRow = (uint8 *)Bitmap->Memory + SourceOffsetY*Bitmap->Pitch + BITMAP_BYTES_PER_PIXEL*SourceOffsetX; uint8 *DestRow = ((uint8 *)Buffer->Memory + MinX*BITMAP_BYTES_PER_PIXEL + MinY*Buffer->Pitch); for(int Y = MinY; Y < MaxY; ++Y) { uint32 *Dest = (uint32 *)DestRow; uint32 *Source = (uint32 *)SourceRow; for(int X = MinX; X < MaxX; ++X) { real32 SA = (real32)((*Source >> 24) & 0xFF); real32 RSA = (SA / 255.0f) * CAlpha; real32 SR = CAlpha*(real32)((*Source >> 16) & 0xFF); real32 SG = CAlpha*(real32)((*Source >> 8) & 0xFF); real32 SB = CAlpha*(real32)((*Source >> 0) & 0xFF); real32 DA = (real32)((*Dest >> 24) & 0xFF); real32 DR = (real32)((*Dest >> 16) & 0xFF); real32 DG = (real32)((*Dest >> 8) & 0xFF); real32 DB = (real32)((*Dest >> 0) & 0xFF); real32 RDA = (DA / 255.0f); real32 InvRSA = (1.0f-RSA); // TODO(casey): Check this for math errors // real32 A = 255.0f*(RSA + RDA - RSA*RDA); real32 A = InvRSA*DA; real32 R = InvRSA*DR; real32 G = InvRSA*DG; real32 B = InvRSA*DB; *Dest = (((uint32)(A + 0.5f) << 24) | ((uint32)(R + 0.5f) << 16) | ((uint32)(G + 0.5f) << 8) | ((uint32)(B + 0.5f) << 0)); ++Dest; ++Source; } DestRow += Buffer->Pitch; SourceRow += Bitmap->Pitch; } } internal void RenderGroupToOutput(render_group *RenderGroup, loaded_bitmap *OutputTarget, rectangle2i ClipRect, bool Even) { IGNORED_TIMED_FUNCTION(); real32 NullPixelsToMeters = 1.0f; for(uint32 BaseAddress = 0; BaseAddress < RenderGroup->PushBufferSize; ) { render_group_entry_header *Header = (render_group_entry_header *) (RenderGroup->PushBufferBase + BaseAddress); BaseAddress += sizeof(*Header); void *Data = (uint8 *)Header + sizeof(*Header); switch(Header->Type) { case RenderGroupEntryType_render_entry_clear: { render_entry_clear *Entry = (render_entry_clear *)Data; DrawRectangle(OutputTarget, V2(0.0f, 0.0f), V2((real32)OutputTarget->Width, (real32)OutputTarget->Height), Entry->Color, ClipRect, Even); BaseAddress += sizeof(*Entry); } break; case RenderGroupEntryType_render_entry_bitmap: { render_entry_bitmap *Entry = (render_entry_bitmap *)Data; Assert(Entry->Bitmap); #if 0 // DrawBitmap(OutputTarget, Entry->Bitmap, P.x, P.y, Entry->Color.a); DrawRectangleSlowly(OutputTarget, Entry->P, V2(Entry->Size.x, 0), V2(0, Entry->Size.y), Entry->Color, Entry->Bitmap, 0, 0, 0, 0, NullPixelsToMeters); #else v2 XAxis = {1, 0}; v2 YAxis = {0, 1}; DrawRectangleQuickly(OutputTarget, Entry->P, Entry->Size.x*XAxis, Entry->Size.y*YAxis, Entry->Color, Entry->Bitmap, NullPixelsToMeters, ClipRect, Even); #endif BaseAddress += sizeof(*Entry); } break; case RenderGroupEntryType_render_entry_rectangle: { render_entry_rectangle *Entry = (render_entry_rectangle *)Data; DrawRectangle(OutputTarget, Entry->P, Entry->P + Entry->Dim, Entry->Color, ClipRect, Even); BaseAddress += sizeof(*Entry); } break; case RenderGroupEntryType_render_entry_coordinate_system: { render_entry_coordinate_system *Entry = (render_entry_coordinate_system *)Data; #if 0 v2 vMax = (Entry->Origin + Entry->XAxis + Entry->YAxis); DrawRectangleSlowly(OutputTarget, Entry->Origin, Entry->XAxis, Entry->YAxis, Entry->Color, Entry->Texture, Entry->NormalMap, Entry->Top, Entry->Middle, Entry->Bottom, PixelsToMeters); v4 Color = {1, 1, 0, 1}; v2 Dim = {2, 2}; v2 P = Entry->Origin; DrawRectangle(OutputTarget, P - Dim, P + Dim, Color); P = Entry->Origin + Entry->XAxis; DrawRectangle(OutputTarget, P - Dim, P + Dim, Color); P = Entry->Origin + Entry->YAxis; DrawRectangle(OutputTarget, P - Dim, P + Dim, Color); DrawRectangle(OutputTarget, vMax - Dim, vMax + Dim, Color); #if 0 for(uint32 PIndex = 0; PIndex < ArrayCount(Entry->Points); ++PIndex) { v2 P = Entry->Points[PIndex]; P = Entry->Origin + P.x*Entry->XAxis + P.y*Entry->YAxis; DrawRectangle(OutputTarget, P - Dim, P + Dim, Entry->Color.r, Entry->Color.g, Entry->Color.b); } #endif #endif BaseAddress += sizeof(*Entry); } break; InvalidDefaultCase; } } } struct tile_render_work { render_group *RenderGroup; loaded_bitmap *OutputTarget; rectangle2i ClipRect; }; internal PLATFORM_WORK_QUEUE_CALLBACK(DoTiledRenderWork) { TIMED_FUNCTION(); tile_render_work *Work = (tile_render_work *)Data; RenderGroupToOutput(Work->RenderGroup, Work->OutputTarget, Work->ClipRect, true); RenderGroupToOutput(Work->RenderGroup, Work->OutputTarget, Work->ClipRect, false); } internal void RenderGroupToOutput(render_group *RenderGroup, loaded_bitmap *OutputTarget) { TIMED_FUNCTION(); Assert(RenderGroup->InsideRender); Assert(((uintptr)OutputTarget->Memory & 15) == 0); rectangle2i ClipRect; ClipRect.MinX = 0; ClipRect.MaxX = OutputTarget->Width; ClipRect.MinY = 0; ClipRect.MaxY = OutputTarget->Height; tile_render_work Work; Work.RenderGroup = RenderGroup; Work.OutputTarget = OutputTarget; Work.ClipRect = ClipRect; DoTiledRenderWork(0, &Work); } internal void TiledRenderGroupToOutput(platform_work_queue *RenderQueue, render_group *RenderGroup, loaded_bitmap *OutputTarget) { TIMED_FUNCTION(); Assert(RenderGroup->InsideRender); /* TODO(casey): - Make sure that tiles are all cache-aligned - Can we get hyperthreads synced so they do interleaved lines? - How big should the tiles be for performance? - Actually ballpark the memory bandwidth for our DrawRectangleQuickly - Re-test some of our instruction choices */ int const TileCountX = 4; int const TileCountY = 4; tile_render_work WorkArray[TileCountX*TileCountY]; Assert(((uintptr)OutputTarget->Memory & 15) == 0); int TileWidth = OutputTarget->Width / TileCountX; int TileHeight = OutputTarget->Height / TileCountY; TileWidth = ((TileWidth + 3) / 4) * 4; int WorkCount = 0; for(int TileY = 0; TileY < TileCountY; ++TileY) { for(int TileX = 0; TileX < TileCountX; ++TileX) { tile_render_work *Work = WorkArray + WorkCount++; rectangle2i ClipRect; ClipRect.MinX = TileX*TileWidth; ClipRect.MaxX = ClipRect.MinX + TileWidth; ClipRect.MinY = TileY*TileHeight; ClipRect.MaxY = ClipRect.MinY + TileHeight; if(TileX == (TileCountX - 1)) { ClipRect.MaxX = OutputTarget->Width; } if(TileY == (TileCountY - 1)) { ClipRect.MaxY = OutputTarget->Height; } Work->RenderGroup = RenderGroup; Work->OutputTarget = OutputTarget; Work->ClipRect = ClipRect; #if 1 // NOTE(casey): This is the multi-threaded path Platform.AddEntry(RenderQueue, DoTiledRenderWork, Work); #else // NOTE(casey): This is the single-threaded path DoTiledRenderWork(RenderQueue, Work); #endif } } Platform.CompleteAllWork(RenderQueue); } internal render_group * AllocateRenderGroup(game_assets *Assets, memory_arena *Arena, uint32 MaxPushBufferSize, b32 RendersInBackground) { render_group *Result = PushStruct(Arena, render_group); if(MaxPushBufferSize == 0) { // TODO(casey): Safe cast from memory_uint to uint32? MaxPushBufferSize = (uint32)GetArenaSizeRemaining(Arena); } Result->PushBufferBase = (uint8 *)PushSize(Arena, MaxPushBufferSize); Result->MaxPushBufferSize = MaxPushBufferSize; Result->PushBufferSize = 0; Result->Assets = Assets; Result->GlobalAlpha = 1.0f; Result->GenerationID = 0; // NOTE(casey): Default transform Result->Transform.OffsetP = V3(0.0f, 0.0f, 0.0f); Result->Transform.Scale = 1.0f; Result->MissingResourceCount = 0; Result->RendersInBackground = RendersInBackground; Result->InsideRender = false; return(Result); } internal void BeginRender(render_group *Group) { IGNORED_TIMED_FUNCTION(); if(Group) { Assert(!Group->InsideRender); Group->InsideRender = true; Group->GenerationID = BeginGeneration(Group->Assets); } } internal void EndRender(render_group *Group) { IGNORED_TIMED_FUNCTION(); if(Group) { Assert(Group->InsideRender); Group->InsideRender = false; EndGeneration(Group->Assets, Group->GenerationID); Group->GenerationID = 0; Group->PushBufferSize = 0; } } inline void Perspective(render_group *RenderGroup, int32 PixelWidth, int32 PixelHeight, real32 MetersToPixels, real32 FocalLength, real32 DistanceAboveTarget) { // TODO(casey): Need to adjust this based on buffer size real32 PixelsToMeters = SafeRatio1(1.0f, MetersToPixels); RenderGroup->MonitorHalfDimInMeters = {0.5f*PixelWidth*PixelsToMeters, 0.5f*PixelHeight*PixelsToMeters}; RenderGroup->Transform.MetersToPixels = MetersToPixels; RenderGroup->Transform.FocalLength = FocalLength; // NOTE(casey): Meters the person is sitting from their monitor RenderGroup->Transform.DistanceAboveTarget = DistanceAboveTarget; RenderGroup->Transform.ScreenCenter = V2(0.5f*PixelWidth, 0.5f*PixelHeight); RenderGroup->Transform.Orthographic = false; RenderGroup->Transform.OffsetP = V3(0, 0, 0); RenderGroup->Transform.Scale = 1.0f; } inline void Orthographic(render_group *RenderGroup, int32 PixelWidth, int32 PixelHeight, real32 MetersToPixels) { real32 PixelsToMeters = SafeRatio1(1.0f, MetersToPixels); RenderGroup->MonitorHalfDimInMeters = {0.5f*PixelWidth*PixelsToMeters, 0.5f*PixelHeight*PixelsToMeters}; RenderGroup->Transform.MetersToPixels = MetersToPixels; RenderGroup->Transform.FocalLength = 1.0f; // NOTE(casey): Meters the person is sitting from their monitor RenderGroup->Transform.DistanceAboveTarget = 1.0f; RenderGroup->Transform.ScreenCenter = V2(0.5f*PixelWidth, 0.5f*PixelHeight); RenderGroup->Transform.Orthographic = true; RenderGroup->Transform.OffsetP = V3(0, 0, 0); RenderGroup->Transform.Scale = 1.0f; } inline entity_basis_p_result GetRenderEntityBasisP(render_transform *Transform, v3 OriginalP) { IGNORED_TIMED_FUNCTION(); entity_basis_p_result Result = {}; v3 P = V3(OriginalP.xy, 0.0f) + Transform->OffsetP; if(Transform->Orthographic) { Result.P = Transform->ScreenCenter + Transform->MetersToPixels*P.xy; Result.Scale = Transform->MetersToPixels; Result.Valid = true; } else { real32 OffsetZ = 0.0f; real32 DistanceAboveTarget = Transform->DistanceAboveTarget; DEBUG_IF(Renderer_Camera_UseDebug) { DEBUG_VARIABLE(r32, Renderer_Camera, DebugDistance); DistanceAboveTarget += DebugDistance; } real32 DistanceToPZ = (DistanceAboveTarget - P.z); real32 NearClipPlane = 0.2f; v3 RawXY = V3(P.xy, 1.0f); if(DistanceToPZ > NearClipPlane) { v3 ProjectedXY = (1.0f / DistanceToPZ) * Transform->FocalLength*RawXY; Result.Scale = Transform->MetersToPixels*ProjectedXY.z; Result.P = Transform->ScreenCenter + Transform->MetersToPixels*ProjectedXY.xy + V2(0.0f, Result.Scale*OffsetZ); Result.Valid = true; } } return(Result); } #define PushRenderElement(Group, type) (type *)PushRenderElement_(Group, sizeof(type), RenderGroupEntryType_##type) inline void * PushRenderElement_(render_group *Group, uint32 Size, render_group_entry_type Type) { IGNORED_TIMED_FUNCTION(); Assert(Group->InsideRender); void *Result = 0; Size += sizeof(render_group_entry_header); if((Group->PushBufferSize + Size) < Group->MaxPushBufferSize) { render_group_entry_header *Header = (render_group_entry_header *)(Group->PushBufferBase + Group->PushBufferSize); Header->Type = Type; Result = (uint8 *)Header + sizeof(*Header); Group->PushBufferSize += Size; } else { InvalidCodePath; } return(Result); } inline used_bitmap_dim GetBitmapDim(render_group *Group, loaded_bitmap *Bitmap, real32 Height, v3 Offset, r32 CAlign) { used_bitmap_dim Dim; Dim.Size = V2(Height*Bitmap->WidthOverHeight, Height); Dim.Align = CAlign*Hadamard(Bitmap->AlignPercentage, Dim.Size); Dim.P = Offset - V3(Dim.Align, 0); Dim.Basis = GetRenderEntityBasisP(&Group->Transform, Dim.P); return(Dim); } inline void PushBitmap(render_group *Group, loaded_bitmap *Bitmap, real32 Height, v3 Offset, v4 Color = V4(1, 1, 1, 1), r32 CAlign = 1.0f) { used_bitmap_dim Dim = GetBitmapDim(Group, Bitmap, Height, Offset, CAlign); if(Dim.Basis.Valid) { render_entry_bitmap *Entry = PushRenderElement(Group, render_entry_bitmap); if(Entry) { Entry->Bitmap = Bitmap; Entry->P = Dim.Basis.P; Entry->Color = Group->GlobalAlpha*Color; Entry->Size = Dim.Basis.Scale*Dim.Size; } } } inline void PushBitmap(render_group *Group, bitmap_id ID, real32 Height, v3 Offset, v4 Color = V4(1, 1, 1, 1), r32 CAlign = 1.0f) { loaded_bitmap *Bitmap = GetBitmap(Group->Assets, ID, Group->GenerationID); if(Group->RendersInBackground && !Bitmap) { LoadBitmap(Group->Assets, ID, true); Bitmap = GetBitmap(Group->Assets, ID, Group->GenerationID); } if(Bitmap) { PushBitmap(Group, Bitmap, Height, Offset, Color, CAlign); } else { Assert(!Group->RendersInBackground); LoadBitmap(Group->Assets, ID, false); ++Group->MissingResourceCount; } } inline loaded_font * PushFont(render_group *Group, font_id ID) { loaded_font *Font = GetFont(Group->Assets, ID, Group->GenerationID); if(Font) { // NOTE(casey): Nothing to do } else { Assert(!Group->RendersInBackground); LoadFont(Group->Assets, ID, false); ++Group->MissingResourceCount; } return(Font); } inline void PushRect(render_group *Group, v3 Offset, v2 Dim, v4 Color = V4(1, 1, 1, 1)) { v3 P = (Offset - V3(0.5f*Dim, 0)); entity_basis_p_result Basis = GetRenderEntityBasisP(&Group->Transform, P); if(Basis.Valid) { render_entry_rectangle *Rect = PushRenderElement(Group, render_entry_rectangle); if(Rect) { Rect->P = Basis.P; Rect->Color = Color; Rect->Dim = Basis.Scale*Dim; } } } inline void PushRect(render_group *Group, rectangle2 Rectangle, r32 Z, v4 Color = V4(1, 1, 1, 1)) { PushRect(Group, V3(GetCenter(Rectangle), Z), GetDim(Rectangle), Color); } inline void PushRectOutline(render_group *Group, v3 Offset, v2 Dim, v4 Color = V4(1, 1, 1, 1), real32 Thickness = 0.1f) { // NOTE(casey): Top and bottom PushRect(Group, Offset - V3(0, 0.5f*Dim.y, 0), V2(Dim.x, Thickness), Color); PushRect(Group, Offset + V3(0, 0.5f*Dim.y, 0), V2(Dim.x, Thickness), Color); // NOTE(casey): Left and right PushRect(Group, Offset - V3(0.5f*Dim.x, 0, 0), V2(Thickness, Dim.y), Color); PushRect(Group, Offset + V3(0.5f*Dim.x, 0, 0), V2(Thickness, Dim.y), Color); } inline void Clear(render_group *Group, v4 Color) { render_entry_clear *Entry = PushRenderElement(Group, render_entry_clear); if(Entry) { Entry->Color = Color; } } inline void CoordinateSystem(render_group *Group, v2 Origin, v2 XAxis, v2 YAxis, v4 Color, loaded_bitmap *Texture, loaded_bitmap *NormalMap, environment_map *Top, environment_map *Middle, environment_map *Bottom) { #if 0 entity_basis_p_result Basis = GetRenderEntityBasisP(RenderGroup, &Entry->EntityBasis, ScreenDim); if(Basis.Valid) { render_entry_coordinate_system *Entry = PushRenderElement(Group, render_entry_coordinate_system); if(Entry) { Entry->Origin = Origin; Entry->XAxis = XAxis; Entry->YAxis = YAxis; Entry->Color = Color; Entry->Texture = Texture; Entry->NormalMap = NormalMap; Entry->Top = Top; Entry->Middle = Middle; Entry->Bottom = Bottom; } } #endif } inline v3 Unproject(render_group *Group, v2 PixelsXY) { render_transform *Transform = &Group->Transform; v2 UnprojectedXY; if(Transform->Orthographic) { UnprojectedXY = (1.0f / Transform->MetersToPixels)*(PixelsXY - Transform->ScreenCenter); } else { v2 A = (PixelsXY - Transform->ScreenCenter) * (1.0f / Transform->MetersToPixels); UnprojectedXY = ((Transform->DistanceAboveTarget - Transform->OffsetP.z)/Transform->FocalLength) * A; } v3 Result = V3(UnprojectedXY, Transform->OffsetP.z); Result -= Transform->OffsetP; return(Result); } inline v2 UnprojectOld(render_group *Group, v2 ProjectedXY, real32 AtDistanceFromCamera) { v2 WorldXY = (AtDistanceFromCamera / Group->Transform.FocalLength)*ProjectedXY; return(WorldXY); } inline rectangle2 GetCameraRectangleAtDistance(render_group *Group, real32 DistanceFromCamera) { v2 RawXY = UnprojectOld(Group, Group->MonitorHalfDimInMeters, DistanceFromCamera); rectangle2 Result = RectCenterHalfDim(V2(0, 0), RawXY); return(Result); } inline rectangle2 GetCameraRectangleAtTarget(render_group *Group) { rectangle2 Result = GetCameraRectangleAtDistance(Group, Group->Transform.DistanceAboveTarget); return(Result); } inline bool32 AllResourcesPresent(render_group *Group) { bool32 Result = (Group->MissingResourceCount == 0); return(Result); }