diff options
Diffstat (limited to 'src')
43 files changed, 13566 insertions, 0 deletions
diff --git a/src/bezier.cpp b/src/bezier.cpp new file mode 100644 index 0000000..6fca5cc --- /dev/null +++ b/src/bezier.cpp @@ -0,0 +1,415 @@ + +static real32 +Bezier_SolveYForX(v2 Point_P0, v2 Point_P1, v2 Point_P2, v2 Point_P3, real32 TargetX) { + + real32 Y = 0; + + v2 m1 = (Point_P2 - Point_P0) / (2 * Tau); + v2 m2 = (Point_P3 - Point_P1) / (2 * Tau); + + real32 Precision = 0.000001; + real32 t = (TargetX - Point_P0.x) / (Point_P3.x - Point_P0.x); + + int Iterations = 0; + for (;;) { + real32 t2 = t * t; + real32 t3 = t2 * t; + real32 mt = 1-t; + real32 mt2 = mt * mt; + real32 mt3 = mt2 * mt; + v2 Point = (Point_P0 * mt3) + (3 * Point_P1 * mt2 * t) + (3 * Point_P2 * mt * t2) + (Point_P3 * t3); + + bool32 Cond1 = (Point.x <= (TargetX - Precision)); + bool32 Cond2 = (Point.x >= (TargetX + Precision)); + + if ((Cond1 || Cond2) && Iterations < 10) { + t = t * TargetX / Point.x; + Iterations++; + } else { + Y = Point.y; + break; + } + } + + return Y; +} + +static bezier_point * +Bezier_LookupAddress(memory *Memory, property_channel *Property, uint16 Index, bool32 AssertExists) +{ + Assert(Index < MAX_KEYFRAMES_PER_BLOCK); // TODO(fox): Test multiple keyframe blocks! + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Property->Block_Bezier_Index[0], AssertExists); + return &Bezier->Point[Index]; +} + +static void +Bezier_Interact_Evaluate(project_state *State, bezier_point *PointAddress, v2 *Pos, real32 GraphZoomHeight, real32 Y_Increment) +{ + Pos[0] = PointAddress->Pos[0]; + Pos[1] = PointAddress->Pos[1]; + Pos[2] = PointAddress->Pos[2]; + if (PointAddress->IsSelected) { + if (State->Interact_Active == interact_type_keyframe_move) { + if (State->Interact_Modifier != 2) + Pos[PointAddress->IsSelected - 1].x += (int32)State->Interact_Offset[0]; + if (State->Interact_Modifier != 1) + Pos[PointAddress->IsSelected - 1].y -= (State->Interact_Offset[1] / GraphZoomHeight / Y_Increment); + } else if (State->Interact_Active == interact_type_keyframe_scale) { + Pos[1].x += State->Interact_Offset[0]; + Pos[2].x -= State->Interact_Offset[0]; + } else if (State->Interact_Active == interact_type_keyframe_rotate) { + // how do I do this?? + Assert(0); + } + } +} + +static void +Bezier_Add(memory *Memory, memory_table_list TableName, property_channel *Property, bezier_point PointData, uint16 *ArrayLocation) +{ + if (!Property->Block_Bezier_Count) { + // TODO(fox): Test multiple keyframe blocks! + Assert(Property->Keyframe_Count < MAX_KEYFRAMES_PER_BLOCK); + Property->Block_Bezier_Index[0] = Memory_Block_AllocateNew(Memory, F_Bezier); + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Property->Block_Bezier_Index[0], 0); + Bezier->Occupied = true; + History_Action_Swap(Memory, TableName, sizeof(Property->Block_Bezier_Count), &Property->Block_Bezier_Count); + Property->Block_Bezier_Count++; + } + // First check to see if the point to add overlaps an existing keyframe: + if (ArrayLocation) { + for (int p = 0; p < Property->Keyframe_Count; p++) { + int k = ArrayLocation[p]; + bezier_point *Point = Bezier_LookupAddress(Memory, Property, k); + if (Point->Pos[0].x == PointData.Pos[0].x) { + History_Action_Swap(Memory, F_Bezier, sizeof(*Point), Point); + *Point = PointData; + return; + } + } + } + int k = 0; + for (;;) { + bezier_point *Point = Bezier_LookupAddress(Memory, Property, k, 0); + if (!Point->Occupied) { + History_Action_Swap(Memory, F_Bezier, sizeof(*Point), Point); + *Point = PointData; + History_Action_Swap(Memory, TableName, sizeof(Property->Keyframe_Count), &Property->Keyframe_Count); + Property->Keyframe_Count++; + return; + } + k++; + } +} + +#if 0 +// A modified version of the bezier code in ImGui with extra features for bitmap and path interaction. + +// Function to convert a ratio back into a point for the bezier handles. +v2 Line_RatioToPoint(v2 a, v2 b, real32 ratio) +{ + v2 ab_dir = b - a; + real32 ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; + real32 dot = ratio*ab_len_sqr; + return a + ab_dir * dot / V2(ab_len_sqr); +} + +// The ratio here is just the dot product divided by the squared length. +v2 Bezier_LineClosestPoint2(v2 a, v2 b, v2 p, real32 *ratio) +{ + v2 ap = p - a; + v2 ab_dir = b - a; + real32 dot = ap.x * ab_dir.x + ap.y * ab_dir.y; + if (dot < 0.0f) { + *ratio = 0.0f; + return a; + } + real32 ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; + if (dot > ab_len_sqr) { + *ratio = 1.0f; + return b; + } + *ratio = dot / ab_len_sqr; + return a + ab_dir * dot / ab_len_sqr; +} + +// Following the algorithm, we take the ratio from the _leftmost_ point in each +// subdivision of the cubic spline until we're within tess_tol, and then we +// interpolate it with the subdivision's rightmost point in the ClosestPoint call. +// The pow(0.5, level) represents the ratio of the next subdivision's leftmost +// point (AKA the rightmost point of the current subdivision). + +// finds the point closest to p and also fills out its ratio along the curve +static void Bezier_CubicClosestPointCasteljauStep(v2 p, v2 *p_closest, real32 ratio, real32 *r_closest, v2 *p_last, real32 *p_closest_dist2, + real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) +{ + real32 dx = x4 - x1; + real32 dy = y4 - y1; + real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + { + v2 p_current = V2(x4, y4); + real32 added_ratio; + v2 p_line = Bezier_LineClosestPoint2(*p_last, p_current, p, &added_ratio); + real32 dist2 = LengthSq(p - p_line); + if (dist2 < *p_closest_dist2) + { + *p_closest = p_line; + *p_closest_dist2 = dist2; + *r_closest = ratio + pow(0.5, level)*added_ratio; + } + *p_last = p_current; + } + else if (level < 10) + { + real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; + real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; + real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; + real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; + real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; + real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; + Bezier_CubicClosestPointCasteljauStep(p, p_closest, ratio, r_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); + Bezier_CubicClosestPointCasteljauStep(p, p_closest, ratio + pow(0.5, level+1), r_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + } +} + +// finds the min/max bounds of the curve +static void Bezier_CubicMinMaxCasteljauStep(v2 *p_min, v2 *p_max, real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) +{ + real32 dx = x4 - x1; + real32 dy = y4 - y1; + real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + { + v2 p_current = V2(x4, y4); + if (p_current.x < p_min->x) { + p_min->x = p_current.x; + } + if (p_current.y < p_min->y) { + p_min->y = p_current.y; + } + if (p_current.x > p_max->x) { + p_max->x = p_current.x; + } + if (p_current.y > p_max->y) { + p_max->y = p_current.y; + } + } + else if (level < 10) + { + real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; + real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; + real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; + real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; + real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; + real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; + Bezier_CubicMinMaxCasteljauStep(p_min, p_max, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); + Bezier_CubicMinMaxCasteljauStep(p_min, p_max, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + } +} + +// return all points +static void Bezier_CubicCalcPointsCasteljauStep(void *Data, uint32 *Increment, real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) +{ + real32 dx = x4 - x1; + real32 dy = y4 - y1; + real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + { + *((real32 *)Data + *Increment*3) = x4; + *((real32 *)Data + *Increment*3 + 1) = y4; + *((real32 *)Data + *Increment*3 + 2) = 0; + *Increment += 1; + } + else if (level < 10) + { + real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; + real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; + real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; + real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; + real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; + real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; + Bezier_CubicCalcPointsCasteljauStep(Data, Increment, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); + Bezier_CubicCalcPointsCasteljauStep(Data, Increment, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + } +} + +real32 Bezier_CubicRatioOfPoint(v2 p1, v2 p2, v2 p3, v2 p4, v2 p) +{ + real32 tess_tol = TESS_TOL; + v2 p_last = p1; + v2 p_closest; + real32 p_closest_dist2 = FLT_MAX; + real32 ratio = 0; + Bezier_CubicClosestPointCasteljauStep(p, &p_closest, 0, &ratio, &p_last, &p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); + return ratio; +} + +void Bezier_CubicCalcPoints(v2 p1, v2 p2, v2 p3, v2 p4, void *Data, uint32 *Increment) +{ + real32 tess_tol = TESS_TOL; + void *Pointer = Data; + Bezier_CubicCalcPointsCasteljauStep(Pointer, Increment, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); +} + +// These functions will become more generalized as shapes are added. + +static void +Mask_AddPointToLine(mask *Mask, uint16 Index, v2 Pos) +{ + for (int i = Mask->NumberOfPoints - 1; i > Index; i--) { + Mask->Point[i+1] = Mask->Point[i]; + } + mask_point *PointToAdd = &Mask->Point[Index+1]; + PointToAdd->Pos = Pos; + Mask->NumberOfPoints++; +} + +static void +Mask_PushPoint(mask *Mask, v2 Pos) +{ + mask_point *PointToAdd = &Mask->Point[Mask->NumberOfPoints]; + PointToAdd->Pos = Pos; + Mask->NumberOfPoints++; +} + +static void +Mask_ShiftPointers(mask *Mask, int16 Increment, int16 StopAt) { + if (Increment > 0) { + int16 i = Mask->NumberOfPoints - 1; + while (i >= StopAt) { + mask_point *CurrentPoint = &Mask->Point[i]; + mask_point *NextPoint = &Mask->Point[i + Increment]; + *NextPoint = *CurrentPoint; + i--; + } + } else { + int16 i = StopAt; + while (i <= Mask->NumberOfPoints - 1) { + mask_point *CurrentPoint = &Mask->Point[i]; + mask_point *NextPoint = &Mask->Point[i - Increment]; + *CurrentPoint = *NextPoint; + i++; + } + } +} + +static void +Mask_DeletePoint(memory *Memory, mask *Mask, uint16 Index) +{ + History_Entry_Commit(Memory, action_entry_default, "Delete keyframe"); + mask_point *MaskPointIndex = &Mask->Point[Index]; + History_Action_StoreData(Memory, MaskPointIndex, sizeof(mask_point)); + + History_Action_Change_Decrement(Memory, &Mask->NumberOfPoints, action_type_change_u16); + // History_Action_Shift(Memory, action_type_shift_bezier, Mask, -1, Index); + void *StartingAddress = &Mask->Point[0]; + History_Action_Shift_2(Memory, StartingAddress, sizeof(mask_point), Mask->NumberOfPoints, -1, Index); + // Mask_ShiftPointers(Mask, -1, Index); + History_Entry_End(Memory); +} + +// It's more useful to input the ratio here instead of the cursor position +// since we have to use it to calculate the four new handle lengths (two on the +// new point and one on each edge). +static void +Mask_AddPointToCurve(memory *Memory, mask *Mask, uint16 Index, real32 ratio) +{ + mask_point *Point0 = &Mask->Point[Index]; + mask_point *Point1 = &Mask->Point[Index+1]; + if (Index + 1 == Mask->NumberOfPoints) + Point1 = &Mask->Point[0]; + + v2 Point0_Pos_Right = Point0->Pos + Point0->TangentRight; + v2 Point1_Pos_Left = Point1->Pos + Point1->TangentLeft; + v2 Handle0_Half = Line_RatioToPoint(Point0->Pos, Point0_Pos_Right, ratio); + v2 Handle1_Half = Line_RatioToPoint(Point1_Pos_Left, Point1->Pos, ratio); + v2 Top_Half = Line_RatioToPoint(Point0_Pos_Right, Point1_Pos_Left, ratio); + v2 NewHandleLeft = Line_RatioToPoint(Handle0_Half, Top_Half, ratio); + v2 NewHandleRight = Line_RatioToPoint(Top_Half, Handle1_Half, ratio); + v2 NewPos = Line_RatioToPoint(NewHandleLeft, NewHandleRight, ratio); + + History_Entry_Commit(Memory, action_entry_default, "Add point to curve"); + + v2 NewPoint0Pos = -(Point0->Pos - Handle0_Half); + v2 NewPoint1Pos = -(Point1->Pos - Handle1_Half); + History_Action_Change_V2(Memory, &Point0->TangentRight, &Point0->TangentRight, &NewPoint0Pos); + History_Action_Change_V2(Memory, &Point1->TangentLeft, &Point1->TangentLeft, &NewPoint1Pos); + + void *StartingAddress = &Mask->Point[0]; + History_Action_Shift_2(Memory, StartingAddress, sizeof(mask_point), Mask->NumberOfPoints, 1, Index); + + mask_point *PointToAdd = &Mask->Point[Index+1]; + + // NOTE(fox): The above shift duplicates the keyframe at Index into where + // we're writing, Index+1. I'm using the Change action (which is normally + // for changing values that already exist) on this intermediate keyframe + // slot to save having to write a bunch of special actions for shifting and + // adding new data. + + History_Action_Change_V2(Memory, &PointToAdd->Pos, &PointToAdd->Pos, &NewPos); + v2 NewLeftPos = -(NewPos - NewHandleLeft); + v2 NewRightPos = -(NewPos - NewHandleRight); + History_Action_Change_V2(Memory, &PointToAdd->TangentLeft, &PointToAdd->TangentLeft, &NewLeftPos); + History_Action_Change_V2(Memory, &PointToAdd->TangentRight, &PointToAdd->TangentRight, &NewRightPos); + + History_Action_Change_Increment(Memory, &Mask->NumberOfPoints, action_type_change_u16); + History_Entry_End(Memory); +} + +static void +Mask_RasterizePoints(mask *Mask) +{ + Mask->NumberOfVerts = 0; + for (int i = 0; i < Mask->NumberOfPoints; i++) { + mask_point Point0 = Mask->Point[i]; + mask_point Point1 = Mask->Point[i+1]; + if (i+1 == Mask->NumberOfPoints) + Point1 = Mask->Point[0]; + + if (Point0.HandleBezier && Point1.HandleBezier) { + Bezier_CubicCalcPoints(Point0.Pos, Point0.Pos + Point0.TangentRight, Point1.Pos + Point1.TangentLeft, Point1.Pos, + Mask->TriangulatedPointCache, &Mask->NumberOfVerts); + } else if (Point0.HandleBezier) { + Bezier_CubicCalcPoints(Point0.Pos, Point0.Pos + Point0.TangentRight, Point1.Pos, Point1.Pos, + Mask->TriangulatedPointCache, &Mask->NumberOfVerts); + } else if (Point1.HandleBezier) { + Bezier_CubicCalcPoints(Point0.Pos, Point0.Pos, Point1.Pos + Point1.TangentLeft, Point1.Pos, + Mask->TriangulatedPointCache, &Mask->NumberOfVerts); + } else { + real32 *Data = (real32 *)Mask->TriangulatedPointCache + Mask->NumberOfVerts*3; + *(Data++) = Point0.Pos.x; + *(Data++) = Point0.Pos.y; + *(Data++) = 0; + // NOTE(fox): CubicCalcPoints sometimes misses generating the start + // point of the next path in the above two cases, so I'm making + // straight lines always add both points as a hotfix. This leads + // to cases of duplicate verts, but it doesn't seem like it harms + // the rendering in any way. + *(Data++) = Point1.Pos.x; + *(Data++) = Point1.Pos.y; + *(Data++) = 0; + Mask->NumberOfVerts += 2; + } + } +} + +void Mask_TriangulateAndRasterize(memory *Memory, project_layer *Layer, mask *Mask) +{ + if (!Mask->TriangulatedPointCache) { + Mask->TriangulatedPointCache = AllocateMemory(Memory, 50*1024, P_VectorPoints); + } + Mask_RasterizePoints(Mask); + + GL_RasterizeShape(Layer, Mask); +} +#endif diff --git a/src/createcalls.cpp b/src/createcalls.cpp new file mode 100644 index 0000000..ee8888d --- /dev/null +++ b/src/createcalls.cpp @@ -0,0 +1,1168 @@ +static void +PostMsg(project_state *State, char *msg) +{ + State->MsgTime = 120; + State->Msg = msg; +} + +static bool32 +File_Open(project_data *File, project_state *State, memory *Memory, char *Filename) +{ + SDL_RWops *FileHandle = SDL_RWFromFile(Filename, "r+b"); + if (!FileHandle) { + return 0; + } + uint64 FileSize = SDL_RWseek(FileHandle, 0, RW_SEEK_END); + void *CompressedData = Memory_PushScratch(Memory, FileSize); + SDL_RWseek(FileHandle, 0, RW_SEEK_SET); + IO_ReadFromStream(CompressedData, FileSize, FileHandle); + SDL_RWclose(FileHandle); + Data_Decompress(Memory, CompressedData, FileSize, File, 0); + Memory_PopScratch(Memory, FileSize); + + // Temp sources aren't cleaned out on file close, so we do it here. + int h = 0, c = 0, i = 0; + int Count = File->Source_Count; + while (Block_Loop(Memory, F_Sources, Count, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + if (Source->Type == source_type_principal_temp) { + Source->Occupied = 0; + File->Source_Count--; + } + } + String_Copy(State->Filename, State->DummyName, 512); + State->Initializing = 4; + Memory->History.NumberOfEntries = 0; + Memory->History.EntryPlayhead = 0; + return 1; +} + +static bool32 +IO_Save(project_data *File, project_state *State, memory *Memory, char *Filename) +{ + SDL_RWops *TestFile = SDL_RWFromFile(Filename, "wb"); + + if (!TestFile) + return 0; + + uint8 *FileEndAddress = (uint8 *)Memory->Slot[F_PrincipalBitmaps].Address; + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Sources, File->Source_Count, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + if (Source->Type == source_type_principal) { + uint64 Size = Source->Width * Source->Height * Source->BytesPerPixel; + void *SourceBitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index, 0); + uint8 *BitmapEnd = (uint8 *)SourceBitmapAddress + Size; + if (BitmapEnd > FileEndAddress) + FileEndAddress = BitmapEnd; + } + } + + Assert(FileEndAddress); + uint64 FileSize = FileEndAddress - (uint8 *)File; + + void *CompressedLocation = Memory_PushScratch(Memory, FileSize); + + uint64 CompressedSize = Data_Compress(Memory, File, FileSize, CompressedLocation, FileSize, 0); // Z_BEST_COMPRESSION); + + Memory_PopScratch(Memory, FileSize); + + IO_WriteToStream(CompressedLocation, CompressedSize, TestFile); + SDL_RWclose(TestFile); + + Memory->IsFileSaved = true; + + return 1; +} + +static void +File_SaveAs(project_data *File, project_state *State, memory *Memory, char *Filename) +{ + if (IO_Save(File, State, Memory, State->Filename)) { + PostMsg(State, "File saved!"); + } else { + PostMsg(State, "File save failed..."); + } +} + +static void +Playhead_Increment(int32 *Frame_Current, int32 Frame_Start, int32 Frame_End, int32 Increment) +{ + *Frame_Current += Increment; + if (*Frame_Current >= Frame_End) { + *Frame_Current = Frame_Start; + } + // if (*Frame_Current < Frame_Start) { + // } +} + +static uint16 +Source_Generate_Blank(project_data *File, project_state *State, memory *Memory, uint16 Width, uint16 Height, uint16 BytesPerPixel) +{ + Assert(File->Source_Count < MAX_SOURCES); + uint16 Index = Memory_Block_AllocateNew(Memory, F_Sources); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Index, 0); + History_Action_Block_Swap(Memory, F_Sources, Source); + Source->Occupied = 1; + Source->Width = Width; + Source->Height = Height; + Source->BytesPerPixel = BytesPerPixel; + Source->Type = source_type_principal; + Source->Bitmap_Index = Memory_Block_PrincipalBitmap_AllocateNew(File, State, Memory); + Source->Path_String_Index = String_AddToFile(Memory, "test"); + History_Action_Swap(Memory, F_File, sizeof(File->Source_Count), &File->Source_Count); + File->Source_Count++; + return Index; +} + +static bool32 Source_IsFileSupported(char *Path, bool32 *IsVideo, bool32 *HasAudio) { + AV_IsFileSupported(Path, IsVideo, HasAudio); + if (IsVideo || HasAudio) { + return 1; + } else { + return stbi_info(Path, NULL, NULL, NULL); + } + Assert(0); + return 0; +} + +static void +Source_Delete(project_data *File, memory *Memory, uint32 Index) +{ + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Index); + History_Action_Block_Swap(Memory, F_Sources, Source); + Source->Occupied = 0; + History_Action_Swap(Memory, F_File, sizeof(File->Source_Count), &File->Source_Count); + File->Source_Count--; +} + +// These thumbnail textures aren't needed for anything else, so I'm just gonna +// count on GL to retain them in GPU memory. +static void +Source_DumpThumbnail(memory *Memory, block_source *Source, uint32 T_Width, uint32 T_Height) +{ + Assert(Source->Type == source_type_principal_temp); + void *BitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index, 0); + uint32 Size = T_Height*T_Width*4; + uint8 *Output = (uint8 *)Memory_PushScratch(Memory, Size); + stbir_resize_uint8((uint8 *)BitmapAddress, Source->Width, Source->Height, 0, Output, T_Height, T_Width, 0, 4); + + GL_GenAndBindTexture(&Source->ThumbnailTex, T_Height, T_Width, 4, Output); + + Memory_PopScratch(Memory, Size); +} + +static int16 +Source_Generate(project_data *File, project_state *State, memory *Memory, void *TempString) +{ + Assert(File->Source_Count < MAX_SOURCES); + + bool32 IsVideo = 0, HasAudio = 0; + if (Source_IsFileSupported((char *)TempString, &IsVideo, &HasAudio)) { + uint16 Index = Memory_Block_AllocateNew(Memory, F_Sources); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Index, 0); + History_Entry_Commit(Memory, "Add source"); + History_Action_Block_Swap(Memory, F_Sources, Source); + + Source->Occupied = 1; + Source->Path_String_Index = String_AddToFile(Memory, (char *)TempString); + Source->Type = source_type_file; + Source->HasAudio = HasAudio; + Source->HasVideo = IsVideo; + + History_Action_Swap(Memory, F_File, sizeof(File->Source_Count), &File->Source_Count); + File->Source_Count++; + History_Entry_End(Memory); + return Index; + } else { + PostMsg(State, "File not supported..."); + } + + return -1; +} + + +static void +Property_AddKeyframe(memory *Memory, memory_table_list TableName, property_channel *Property, int Frame, uint16 *ArrayLocation) +{ + History_Entry_Commit(Memory, "Add keyframe"); + bezier_point Point = { 1, {(real32)Frame, Property->CurrentValue, -1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 }; + Bezier_Add(Memory, TableName, Property, Point, ArrayLocation); + History_Entry_End(Memory); +} + +static property_channel +Property_InitFloat(real32 Val, real32 ScrubVal, real32 MinVal, real32 MaxVal, bool32 AlwaysInteger) { + property_channel Property = {}; + Property.CurrentValue = Val; + Property.MinVal = MinVal; + Property.MaxVal = MaxVal; + Property.AlwaysInteger = AlwaysInteger; + Property.ScrubVal = ScrubVal; + return Property; +} + +static block_composition * +Precomp_Init(project_data *File, memory *Memory) +{ + if (File->Comp_Count + 1 > MAX_COMPS) { + Assert(0); + } + block_composition *Comp = (block_composition *)Memory_Block_AllocateAddress(Memory, F_Precomps); + History_Action_Block_Swap(Memory, F_Precomps, Comp); + + *Comp = {}; + Comp->Occupied = 1; + + Comp->Name_String_Index = Memory_Block_AllocateNew(Memory, F_Strings); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Comp->Name_String_Index, 0); + sprintf(String->Char, "Comp %i", File->Comp_Count); + String->Occupied = 1; + + History_Action_Swap(Memory, F_File, sizeof(File->Comp_Count), &File->Comp_Count); + File->Comp_Count++; + return Comp; +} + +static uint32 +Effect_Init(project_state *State, memory *Memory, uint32 EffectEntryIndex, int EffectCount) +{ + uint16 EffectAddressIndex = Memory_Block_AllocateNew(Memory, F_Effects); + block_effect *Effect = (block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, EffectAddressIndex, 0); + History_Action_Block_Swap(Memory, F_Effects, Effect); + *Effect = {}; + Effect->Occupied = true; + header_effect *EffectHeader = &State->Effect[EffectEntryIndex]; + String_Copy(Effect->ID, EffectHeader->ID, 8); + Effect->IsToggled = true; + for (int e = 0; e < EffectHeader->Property_Count; e++) { + Effect->Block_Property_Index[e] = Memory_Block_AllocateNew(Memory, F_Properties); + property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[e], 0); + History_Action_Block_Swap(Memory, F_Properties, Property); + Property->Occupied = true; + header_property PropertyHeader = State->Property[EffectHeader->PropertyStartIndex + e]; + Property->Identifier = -1; + Property->CurrentValue = PropertyHeader.DefaultValue; + Property->MinVal = PropertyHeader.MinVal; + Property->MaxVal = PropertyHeader.MaxVal; + } + return EffectAddressIndex; +} + +static void +Effect_Add(project_data *File, project_state *State, memory *Memory, uint32 EffectEntryIndex) +{ + History_Entry_Commit(Memory, "Add effect"); + int h = 0, c = 0, i = 0; + int Selected = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) + { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected) { + Layer->Block_Effect_Index[Layer->Block_Effect_Count] = Effect_Init(State, Memory, EffectEntryIndex, Layer->Block_Effect_Count); + History_Action_Swap(Memory, F_File, sizeof(Layer->Block_Effect_Count), &Layer->Block_Effect_Count); + Layer->Block_Effect_Count++; + Selected++; + } + } + History_Entry_End(Memory); + Assert(Selected); +} + +void Source_DeselectAll(project_data *File, memory *Memory) +{ + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Sources, File->Source_Count, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + Source->IsSelected = 0; + } +} + + +static void +Keyframe_Commit(project_data *File, project_state *State, memory *Memory, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + History_Entry_Commit(Memory, "Move keyframe"); + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, Layer->Block_Composition_Index); + if ((State->TimelineMode == timeline_mode_graph) && !Layer->IsSelected) + continue; + sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayerStart->SortedPropertyStart; + uint16 *ArrayLocation = SortedKeyframeArray + SortedLayerStart->SortedKeyframeStart; + int h = 0, c = 0, p = 0; + property_channel *Property = NULL; + block_effect *Effect = NULL; + while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) + { + if ((State->TimelineMode != timeline_mode_graph) && !Property->IsToggled) + continue; + for (int p = 0; p < Property->Keyframe_Count; p++) { + int k = ArrayLocation[p]; + bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, k); + if (PointAddress->IsSelected) { + v2 NewPos[3]; + Bezier_Interact_Evaluate(State, PointAddress, NewPos); + History_Action_Swap(Memory, F_Bezier, sizeof(PointAddress->Pos), &PointAddress->Pos); + PointAddress->Pos[0] = NewPos[0]; + PointAddress->Pos[1] = NewPos[1]; + PointAddress->Pos[2] = NewPos[2]; + } + } + } + } + History_Entry_End(Memory); + State->Interact_Offset[0] = 0; + State->Interact_Offset[1] = 0; + State->Interact_Offset[2] = 0; + State->Interact_Offset[3] = 0; + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; +} + +// NOTE(fox): This won't work with precomps! + +void Clipboard_Paste(project_data *File, project_state *State, memory *Memory, sorted_comp_array *SortedCompStart, sorted_layer_array *SortedLayerStart, uint16 *SortedKeyframeArray) +{ + clipboard_contents *Contents = (clipboard_contents *)State->ClipboardBuffer; + if (Contents->Type == selection_type_none) + return; + uint64 ClipboardPos = sizeof(clipboard_contents); + ClipboardPos = sizeof(clipboard_contents); + int i = SortedCompStart->LayerCount - 1; + block_layer *Layer = NULL; + clipboard_channel *Channel; + int b = 0; + int LayerCount = 0; + + int NumberOfLayersFromClipboard = 1; + int LastOffset = 0; + for (int a = 0; a < Contents->ChannelCount; a++) { + Channel = &Contents->Channel[a]; + if (a != 0) { + if (Channel->LayerOffset != LastOffset) + NumberOfLayersFromClipboard++; + } + LastOffset = Channel->LayerOffset; + } + + for (;;) { + Channel = &Contents->Channel[b]; + while (i >= 0) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *TestLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (TestLayer->IsSelected) { + Layer = TestLayer; + break; + } + i--; + } + if (Layer == NULL) + break; + // NOTE(fox): This loop assumes all layers and the clipboard have + // channels laid out in the same way! + Assert(0); + /* + for (int h = 0; h < AmountOf(Layer->Property); h++) { + property_channel *Property = &Layer->Property[h]; + if (Property->Name == Channel->Name) { + for (int p = 0; p < Channel->KeyframeCount; p++) { + bezier_point PointData = *(bezier_point *)((uint8 *)State->ClipboardBuffer + ClipboardPos); + PointData.Pos[0].x += State->Frame_Current; + uint16 *ArrayLocation = Property_GetSortedArray(SortedKeyframeArray, i, h); + Bezier_Add(Memory, F_Layers, Property, PointData, ArrayLocation); + ClipboardPos += sizeof(bezier_point); + } + b++; + Channel = &Contents->Channel[b]; + } + } + Layer = NULL; + if (b < Contents->ChannelCount) { + if (NumberOfLayersFromClipboard != 1) + break; + else + b = 0; + } + */ + } +} + +void Clipboard_Store(project_data *File, project_state *State, memory *Memory, sorted_comp_array *SortedCompStart, sorted_layer_array *SortedLayerStart, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + int LocalOffset = 0; + clipboard_contents *Contents = (clipboard_contents *)State->ClipboardBuffer; + *Contents = {}; + Contents->Type = State->RecentSelectionType; + uint64 ClipboardPos = sizeof(clipboard_contents); + if (Contents->Type == selection_type_none) + return; + else if (Contents->Type == selection_type_keyframe) { + for (int i = SortedCompStart->LayerCount - 1; i >= 0; i--) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + for (int h = 0; h < AmountOf(Layer->Property); h++) { + property_channel *Property = &Layer->Property[h]; + if (Property->IsToggled || Layer->IsSelected) { + sorted_property_array *InfoLocation = Property_GetSortedInfo(SortedPropertyStart, i, h); + uint16 *ArrayLocation = Property_GetSortedArray(SortedKeyframeArray, i, h); + clipboard_channel *Channel = &Contents->Channel[Contents->ChannelCount]; + bezier_point *FirstPoint = NULL; + int TimeOffset = 0; + for (int p = 0; p < Property->Keyframe_Count; p++) { + bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, ArrayLocation[p]); + if (PointAddress->IsSelected) { + if (!FirstPoint) { + FirstPoint = PointAddress; + TimeOffset = FirstPoint->Pos[0].x; + } + bezier_point PointToCopy = *PointAddress; + PointToCopy.Pos[0].x -= TimeOffset; + Memory_Copy((uint8 *)State->ClipboardBuffer + ClipboardPos, (uint8 *)&PointToCopy, sizeof(bezier_point)); + ClipboardPos += sizeof(bezier_point); + Channel->KeyframeCount++; + } + } + if (Channel->KeyframeCount) { + if (!LocalOffset) + LocalOffset = i; + Contents->ChannelCount++; + Channel->LayerOffset = LocalOffset - i; + Assert(0); + // Channel->Name = Property->Name; + } + } + } + } + } + else if (Contents->Type == selection_type_layer) { + } +} + +void Property_MinMax_X(memory *Memory, project_state *State, property_channel *Property, + uint16 *ArrayLocation, real32 *Min, real32 *Max) +{ + v2 FirstPointPos[3]; + bezier_point *FirstPointAddress = Bezier_LookupAddress(Memory, Property, ArrayLocation[0]); + Bezier_Interact_Evaluate(State, FirstPointAddress, FirstPointPos); + *Min = FirstPointPos[0].x; + v2 LastPointPos[3]; + bezier_point *LastPointAddress = Bezier_LookupAddress(Memory, Property, ArrayLocation[Property->Keyframe_Count - 1]); + Bezier_Interact_Evaluate(State, LastPointAddress, LastPointPos); + *Max = LastPointPos[0].x; +} +void Property_MinMax_Y(memory *Memory, project_state *State, property_channel *Property, + sorted_property_array *PropertyStart, real32 *Min, real32 *Max, bool32 Evaluate = 1) +{ + if (Evaluate) { + v2 MinYPointPos[3]; + bezier_point *MinYPointAddress = Bezier_LookupAddress(Memory, Property, PropertyStart->MinYIndex); + Bezier_Interact_Evaluate(State, MinYPointAddress, MinYPointPos); + *Min = MinYPointPos[0].y; + v2 MaxYPointPos[3]; + bezier_point *MaxYPointAddress = Bezier_LookupAddress(Memory, Property, PropertyStart->MaxYIndex); + Bezier_Interact_Evaluate(State, MaxYPointAddress, MaxYPointPos); + *Max = MaxYPointPos[0].y; + } else { + bezier_point *MinYPointAddress = Bezier_LookupAddress(Memory, Property, PropertyStart->MinYIndex); + *Min = MinYPointAddress->Pos[0].y; + bezier_point *MaxYPointAddress = Bezier_LookupAddress(Memory, Property, PropertyStart->MaxYIndex); + *Max = MaxYPointAddress->Pos[0].y; + } +} + +inline property_channel * +Effect_Property(memory *Memory, block_effect *Effect, int Offset) +{ + return (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[Offset]); +}; + +inline v2 Effect_V2(memory *Memory, block_effect *Effect, int Offset) +{ + property_channel *Property_X = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[Offset]); + property_channel *Property_Y = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[Offset + 1]); + return V2(Property_X->CurrentValue, Property_Y->CurrentValue); +} + +// TODO(fox): Merge with other sorting code. +void Effect_Curves_Sort(memory *Memory, block_effect *Effect, uint16 *SortedPointStart, uint16 Which) +{ + int i = 0; + int SortedPoints = 0; + for (;;) { + property_channel *CurrentProperty = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[i]); + v2 Point = Effect_V2(Memory, Effect, i); + uint32 SortedIndex_Playhead = 0; + if (CurrentProperty->Identifier == Which) { + while (SortedIndex_Playhead < SortedPoints) { + uint16 TestPointEntry = SortedPointStart[SortedIndex_Playhead]; + Assert(((property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[TestPointEntry]))->Identifier == Which); + v2 TestPoint = Effect_V2(Memory, Effect, TestPointEntry); + if (Point.x < TestPoint.x) { + break; + } else { + SortedIndex_Playhead += 1; + } + } + if (SortedIndex_Playhead != SortedPoints) { + uint8 *Address_Start = (uint8 *)(SortedPointStart + SortedIndex_Playhead); + uint8 *Address_End = (uint8 *)(SortedPointStart + SortedPoints) - 1; + Arbitrary_ShiftData(Address_Start, Address_End, sizeof(uint16), 1); + } + + uint16 *PointEntry = SortedPointStart + SortedIndex_Playhead; + *PointEntry = i; + SortedPoints++; + } + i += 2; + if (i > (MAX_PROPERTIES_PER_EFFECT - 1)) + break; + } +} + +static void +Interact_Evaluate_Layer(memory *Memory, project_state *State, uint16 Layer_Index_Physical, sorted_comp_array SortedCompStart, sorted_layer_array *SortedLayerStart, + int32 *Frame_Start, int32 *Frame_End) +{ + if (State->Interact_Active == interact_type_layer_move) { + *Frame_Start += (int32)(State->Interact_Offset[0] + 0); + *Frame_End += (int32)(State->Interact_Offset[0] + 0); + } + if (State->Interact_Active == interact_type_layer_timeadjust) { + int Side[2] = {0}; + Assert(State->Interact_Offset[1] == 0 || State->Interact_Offset[1] == 1); + Side[(int)State->Interact_Offset[1]] = 1; + *Frame_Start += (int32)(State->Interact_Offset[0] * Side[0]); + if (*Frame_Start >= *Frame_End) + *Frame_Start = *Frame_End - 1; + *Frame_End += (int32)(State->Interact_Offset[0] * Side[1]); + if (*Frame_End <= *Frame_Start) + *Frame_End = *Frame_Start + 1; + } +} + +void File_DeselectAllKeyframes(project_data *File, project_state *State, memory *Memory, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (!Layer->IsSelected) + continue; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, Layer->Block_Composition_Index); + sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayerStart->SortedPropertyStart; + uint16 *ArrayLocation = SortedKeyframeArray + SortedLayerStart->SortedKeyframeStart; + int h = 0, c = 0, p = 0; + property_channel *Property = NULL; + block_effect *Effect = NULL; + while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) + { + for (int p = 0; p < Property->Keyframe_Count; p++) { + int k = ArrayLocation[p]; + bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, k); + PointAddress->IsSelected = 0; + } + } + } +} + + +void Layer_Evaluate_Display(project_state *State, memory *Memory, block_layer *Layer, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray, + sorted_layer_array *LayerArrayStart, sorted_comp_array *CompStart, sorted_layer_array *SortedLayerStart, + int i, real32 *Offset) +{ + int ExtraPad = 1; + sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayerStart->SortedPropertyStart; + uint16 *ArrayLocation = SortedKeyframeArray + SortedLayerStart->SortedKeyframeStart; + int h = 0, c = 0, p = 0; + property_channel *Property = NULL; + block_effect *Effect = NULL; + while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) + { + if (Property->IsToggled) { + *Offset += 1 + ExtraPad; + ExtraPad = 0; + } + } + /* + if (Layer->Precomp_Toggled) { + Assert(Layer->IsPrecomp); + sorted_comp_array *Layer_SortedCompStart = &CompStart[Layer->Block_Source_Index]; + sorted_layer_array *Layer_SortedLayerStart = Sorted_GetLayerStart(LayerArrayStart, CompStart, Layer->Block_Source_Index); + sorted_layer_array *TopLayerEntry = &Layer_SortedLayerStart[0]; + sorted_layer_array *BottomLayerEntry = &Layer_SortedLayerStart[Layer_SortedCompStart->LayerCount - 1]; + *Offset += TopLayerEntry->SortedOffset - BottomLayerEntry->SortedOffset + 2; + } + */ +} + +static void +Project_Layer_Delete(project_data *File, project_state *State, memory *Memory) +{ + bool32 CommitAction = 0; + int h = 0, c = 0, i = 0; + int LayerCount = File->Layer_Count; + while (Block_Loop(Memory, F_Layers, LayerCount, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected) { + if (!CommitAction) { + History_Entry_Commit(Memory, "Delete layer"); + CommitAction = 1; + } + Layer_Delete(File, State, Memory, i); + } + } + if (CommitAction) { + History_Entry_End(Memory); + State->UpdateFrame = true; + State->MostRecentlySelectedLayer = -1; + } +} + +static bool32 +Property_IsGraphSelected(memory *Memory, property_channel *Property, uint16 *ArrayLocation) +{ + for (int p = 0; p < Property->Keyframe_Count; p++) { + int k = ArrayLocation[p]; + bezier_point *Point = Bezier_LookupAddress(Memory, Property, k); + if (Point->IsSelected) + return 1; + } + return 0; +} + +static void +Project_PaintLayer_New(project_data *File, project_state *State, memory *Memory) +{ + int TopOffset = Layer_GetTopOffset(File, Memory); + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + Arbitrary_Zero((uint8 *)State->Brush.TransientBitmap, 2048*2048*4); + State->Interact_Active = interact_type_brush; + Layer_DeselectAll(File, State, Memory); + History_Entry_Commit(Memory, "Paint new layer"); + uint16 i = Source_Generate_Blank(File, State, Memory, MainComp->Width, MainComp->Height, MainComp->BytesPerPixel); + block_layer *Layer = Layer_Init(File, Memory); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + State->Brush.LayerToPaint_Index = Memory_Block_LazyIndexAtAddress(Memory, F_Layers, (void *)Layer); + Layer->Block_Source_Index = i; + Layer->x.CurrentValue = MainComp->Width / 2; + Layer->y.CurrentValue = MainComp->Height / 2; + if (File->Layer_Count == 1) { + Layer->Vertical_Offset = 11 - 1; + } else { + Layer->Vertical_Offset = TopOffset - 1; + } + Layer->Frame_Start = MainComp->Frame_Start; + Layer->Frame_End = MainComp->Frame_End; + Layer_Select(Memory, State, Memory_Block_LazyIndexAtAddress(Memory, F_Layers, Layer)); + History_Entry_End(Memory); + State->UpdateFrame = true; +} + +void Source_UICreateButton(project_data *File, project_state *State, memory *Memory) +{ + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + int TopOffset = Layer_GetTopOffset(File, Memory); + bool32 CommitAction = 0; + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Sources, File->Source_Count, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + if (Source->IsSelected) { + if (!CommitAction) { + History_Entry_Commit(Memory, "Create layer from source"); + CommitAction = 1; + } + if (Source->Type == source_type_principal_temp) { + History_Action_Swap(Memory, F_File, sizeof(Source->Type), &Source->Type); + Source->Type = source_type_principal; + } + block_layer *Layer = Layer_Init(File, Memory); + Layer->Block_Source_Index = i; + Layer->x.CurrentValue = Comp->Width/2; + Layer->y.CurrentValue = Comp->Height/2; + Layer->Vertical_Offset = TopOffset-1; + Layer->Frame_Start = Comp->Frame_Start; + Layer->Frame_End = Comp->Frame_End; + } + State->UpdateFrame = true; + } + if (CommitAction) + History_Entry_End(Memory); + Source_DeselectAll(File, Memory); +} + +void Precomp_UIDuplicate(project_data *File, project_state *State, memory *Memory, uint16 CompIndex, + sorted_comp_array SortedCompStart, sorted_layer_array *SortedLayerStart) +{ + block_layer *Layer = NULL; + for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsSelected) { + break; + } + } + if (Layer) { + block_layer *DupeLayer = Layer_Init(File, Memory); + *DupeLayer = *Layer; + DupeLayer->Vertical_Offset += 1; + for (int h = 0; h < AmountOf(Layer->Property); h++) { + property_channel *Property = &Layer->Property[h]; + if (Property->Block_Bezier_Count) { + property_channel *DupeProperty = &DupeLayer->Property[h]; + DupeProperty->Block_Bezier_Index[0] = Memory_Block_AllocateNew(Memory, F_Bezier); + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Property->Block_Bezier_Index[0]); + block_bezier *DupeBezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, DupeProperty->Block_Bezier_Index[0], 0); + Bezier->Occupied = true; + *DupeBezier = *Bezier; + } + } + } +} + +void Precomp_UIDelete(project_data *File, project_state *State, memory *Memory, uint16 CompIndex, + sorted_comp_array SortedCompStart, sorted_layer_array *SortedLayerStart) +{ + block_layer *Layer = NULL; + for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsSelected) { + } + } +} + +void Precomp_UICreateButton(project_data *File, project_state *State, memory *Memory, uint16 CompIndex, + sorted_comp_array SortedCompStart, sorted_layer_array *SortedLayerStart) +{ + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex); + History_Entry_Commit(Memory, "Pre-compose layer"); + block_composition *NewComp = Precomp_Init(File, Memory); + NewComp->Width = Comp->Width; + NewComp->Height = Comp->Height; + NewComp->FPS = Comp->FPS; + NewComp->BytesPerPixel = Comp->BytesPerPixel; + NewComp->Frame_Count = Comp->Frame_Count; + NewComp->Frame_Start = Comp->Frame_Start; + NewComp->Frame_End = Comp->Frame_End; + int32 TopOffset = 0; + for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsSelected) { + TopOffset = Layer->Vertical_Offset; + break; + } + } + for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsSelected) { + History_Action_Swap(Memory, F_File, sizeof(Layer->Block_Composition_Index), &Layer->Block_Composition_Index); + Layer->Block_Composition_Index = File->Comp_Count - 1; + } + } + block_layer *PrecompLayer = Layer_Init(File, Memory); + bezier_point Point0 = { 1, {0, 0, 1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 }; + bezier_point Point1 = { 1, {(real32)NewComp->Frame_End, (real32)NewComp->Frame_End, 1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 }; + Bezier_Add(Memory, F_Layers, &PrecompLayer->time, Point0, NULL); + Bezier_Add(Memory, F_Layers, &PrecompLayer->time, Point1, NULL); + PrecompLayer->IsPrecomp = true; + Layer_Select(Memory, State, Memory_Block_LazyIndexAtAddress(Memory, F_Layers, PrecompLayer)); + PrecompLayer->Block_Source_Index = File->Comp_Count - 1; + PrecompLayer->Block_Composition_Index = CompIndex; + PrecompLayer->Vertical_Offset = TopOffset; + PrecompLayer->Frame_End = NewComp->Frame_End; + PrecompLayer->ColIndex = 3; + PrecompLayer->x.CurrentValue = Comp->Width/2; + PrecompLayer->y.CurrentValue = Comp->Height/2; + History_Entry_End(Memory); + State->UpdateFrame = true; +} + + +// Helper for working with different bit depths. +static render_byte_info +Bitmap_ByteInfo(uint32 BytesPerPixel) { + render_byte_info Byte = {}; + if (BytesPerPixel == 4) { + Byte.MaskPixel = 0xFF; + Byte.ByteOffset = 1; + Byte.Normalized = 1 / 255.0f; + Byte.Bits = 255; + } else if (BytesPerPixel == 8) { + Byte.MaskPixel = 0xFFFF; + Byte.ByteOffset = 2; + Byte.Normalized = 1 / 65535.0f; + Byte.Bits = 65535; + } else { + Byte.MaskPixel = 0xFFFFFFFF; + Byte.ByteOffset = 4; + Byte.Normalized = 1 / 4294967295.0f; + Byte.Bits = 4294967295; + Assert(0); + } + return Byte; +} + +// TODO(fox): Make separate full-size bitmap that gets scaled on the GPU instead of this +static void +State_BindBrushTexture(memory *Memory, brush_state *Brush, uint32 BytesPerPixel) +{ + GL_GenAndBindTexture(&Brush->GLTexture, Brush->Size, Brush->Size, BytesPerPixel, Brush->PaintBuffer); +} + +static void +Brush_CalcBitmapAlphaFromSize(memory *Memory, brush_state *Brush, uint32 BytesPerPixel) +{ + int32 BrushLength = Brush->Size; + if (BrushLength > 128) { + BrushLength = BrushLength + (16 - (BrushLength % 16)); + } + real32 BrushCenter = (real32)BrushLength / 2; + real32 MaxLength = BrushCenter; + void *BrushAddress = Brush->PaintBuffer; + + render_byte_info Byte = Bitmap_ByteInfo(BytesPerPixel); + + for (int Y = 0; Y < BrushLength; Y++) { + for (int X = 0; X < BrushLength; X++) { + uint8 *PixelAddress = (uint8 *)BrushAddress + (Y * BytesPerPixel*BrushLength) + (X * BytesPerPixel); + uint32 *R_DestAddress = (uint32 *)(PixelAddress + Byte.ByteOffset*0); + // uint32 *G_DestAddress = (uint32 *)(PixelAddress + Byte.ByteOffset*1); + // uint32 *B_DestAddress = (uint32 *)(PixelAddress + Byte.ByteOffset*2); + uint32 *A_DestAddress = (uint32 *)(PixelAddress + Byte.ByteOffset*3); + v2 Pos = V2(BrushCenter - X, BrushCenter - Y); + real32 L = sqrt(LengthSq(Pos)); + real32 Gradient = Ceil(L, MaxLength) / MaxLength; + Gradient = pow(Gradient, Brush->Hardness); + // Gradient = (Gradient >= 0.04045) ? pow((Gradient + 0.055) / (1 + 0.055), 2.4) : Gradient / 12.92; + // Gradient = (Gradient >= 0.0031308) ? (1.055) * pow(Gradient, (1.0/2.4)) - 0.055 : 12.92 * Gradient; + *R_DestAddress = (*R_DestAddress & ~Byte.MaskPixel) | Byte.Bits; // brush preview is red + *A_DestAddress = (*A_DestAddress & ~Byte.MaskPixel) | (uint32)((1.0f - Gradient)*Byte.Bits); + } + } +} + +// Imported bitmaps are stored in linear, and all ops are also done in linear. +static void +Bitmap_SRGBToLinear(void *Buffer, uint16 Width, uint16 Height, uint16 BytesPerPixel, bool32 ToLinear) +{ + uint8 *Row = (uint8 *)Buffer; + uint64 TotalBytes = Width * Height * BytesPerPixel; + + render_byte_info LayerBits = Bitmap_ByteInfo(BytesPerPixel); + + uint64 bytes = 0; + while (bytes < TotalBytes) { + uint8 *LayerPixel = Row + bytes; + uint32 *R_DestAddress = (uint32 *)(LayerPixel + LayerBits.ByteOffset * 0); + uint32 *G_DestAddress = (uint32 *)(LayerPixel + LayerBits.ByteOffset * 1); + uint32 *B_DestAddress = (uint32 *)(LayerPixel + LayerBits.ByteOffset * 2); + uint32 *A_DestAddress = (uint32 *)(LayerPixel + LayerBits.ByteOffset * 3); + + real32 TexR = (real32)(*R_DestAddress & LayerBits.MaskPixel) * LayerBits.Normalized; + real32 TexG = (real32)(*G_DestAddress & LayerBits.MaskPixel) * LayerBits.Normalized; + real32 TexB = (real32)(*B_DestAddress & LayerBits.MaskPixel) * LayerBits.Normalized; + real32 TexA = (real32)(*A_DestAddress & LayerBits.MaskPixel) * LayerBits.Normalized; + + if (ToLinear) { + TexR = (TexR >= 0.04045) ? pow((TexR + 0.055) / (1 + 0.055), 2.4) : TexR / 12.92; + TexG = (TexG >= 0.04045) ? pow((TexG + 0.055) / (1 + 0.055), 2.4) : TexG / 12.92; + TexB = (TexB >= 0.04045) ? pow((TexB + 0.055) / (1 + 0.055), 2.4) : TexB / 12.92; + TexA = (TexA >= 0.04045) ? pow((TexA + 0.055) / (1 + 0.055), 2.4) : TexA / 12.92; + } else { + TexR = (TexR >= 0.0031308) ? (1.055) * pow(TexR, (1.0/2.4)) - 0.055 : 12.92 * TexR; + TexG = (TexG >= 0.0031308) ? (1.055) * pow(TexG, (1.0/2.4)) - 0.055 : 12.92 * TexG; + TexB = (TexB >= 0.0031308) ? (1.055) * pow(TexB, (1.0/2.4)) - 0.055 : 12.92 * TexB; + TexA = (TexA >= 0.0031308) ? (1.055) * pow(TexA, (1.0/2.4)) - 0.055 : 12.92 * TexA; + } + + uint32 R_Out = (uint32)(Normalize(TexR) * LayerBits.Bits); + uint32 G_Out = (uint32)(Normalize(TexG) * LayerBits.Bits); + uint32 B_Out = (uint32)(Normalize(TexB) * LayerBits.Bits); + uint32 A_Out = (uint32)(Normalize(TexA) * LayerBits.Bits); + + *R_DestAddress = (*R_DestAddress & ~LayerBits.MaskPixel) | R_Out; + *G_DestAddress = (*G_DestAddress & ~LayerBits.MaskPixel) | G_Out; + *B_DestAddress = (*B_DestAddress & ~LayerBits.MaskPixel) | B_Out; + *A_DestAddress = (*A_DestAddress & ~LayerBits.MaskPixel) | A_Out; + bytes += BytesPerPixel; + } +} + +static void +Brush_Info(brush_info *B, brush_state *Brush, block_source *Source, void *SourceBuffer, v2 LayerPos, v4 Color) +{ + B->BrushLength = (uint32)Brush->Size; + if (B->BrushLength > 128) { + B->BrushLength = B->BrushLength + (16 - (B->BrushLength % 16)); + } + + rectangle RenderRegion = { 0, 0, Source->Width, Source->Height }; + rectangle BrushPos = { (int32)(LayerPos.x - (B->BrushLength / 2)), + (int32)(LayerPos.y - (B->BrushLength / 2)), + (int32)(LayerPos.x + (B->BrushLength / 2)), + (int32)(LayerPos.y + (B->BrushLength / 2)) }; + + B->LayerBounds = ClipRectangle(BrushPos, RenderRegion); + + if (BrushPos.Min.x < Brush->CacheBounds.Min.x) + Brush->CacheBounds.Min.x = BrushPos.Min.x; + if (BrushPos.Min.y < Brush->CacheBounds.Min.y) + Brush->CacheBounds.Min.y = BrushPos.Min.y; + if (BrushPos.Max.x > Brush->CacheBounds.Max.x) + Brush->CacheBounds.Max.x = BrushPos.Max.x; + if (BrushPos.Max.y > Brush->CacheBounds.Max.y) + Brush->CacheBounds.Max.y = BrushPos.Max.y; + + Assert(Source->Type == source_type_principal); + + B->BytesPerPixel = 4; + B->SourceWidth = Source->Width; + B->SourceBytesPerPixel = Source->BytesPerPixel; + B->SourceBuffer = SourceBuffer; + + Assert(Source->BytesPerPixel == 4); + + B->LayerPitch = Source->Width*Source->BytesPerPixel; + B->BrushPitch = (int)B->BrushLength * B->BytesPerPixel; + + B->LayerBits = Bitmap_ByteInfo(Source->BytesPerPixel); + B->BrushBits = Bitmap_ByteInfo(B->BytesPerPixel); + + B->ExtraX = 0; + B->ExtraY = 0; + if (BrushPos.Min.x < 0) { + B->ExtraX = BrushPos.Min.x; + } + if (BrushPos.Min.y < 0) { + B->ExtraY = BrushPos.Min.y; + } + + B->BrushBuffer = Brush->PaintBuffer; + B->EraseMode = Brush->EraseMode; + + B->R_Brush = (Color.r >= 0.04045) ? pow((Color.r + 0.055) / (1 + 0.055), 2.4) : Color.r / 12.92; + B->G_Brush = (Color.g >= 0.04045) ? pow((Color.g + 0.055) / (1 + 0.055), 2.4) : Color.g / 12.92; + B->B_Brush = (Color.b >= 0.04045) ? pow((Color.b + 0.055) / (1 + 0.055), 2.4) : Color.b / 12.92; + B->A_Brush = (Color.a >= 0.04045) ? pow((Color.a + 0.055) / (1 + 0.055), 2.4) : Color.a / 12.92; + + B->BrushRow = (uint8 *)B->BrushBuffer; +} + +void Bitmap_SwapData(uint8 *Address_0, uint8 *Address_1, uint64 Size, uint16 BytesPerPixel) +{ + uint64 i = 0; + uint16 ByteOffset = Bitmap_ByteInfo(BytesPerPixel).ByteOffset; + uint64 RemainderBytes = Size % ByteOffset; + Assert(BytesPerPixel == 4); + + while (i < Size) { + uint32 *Pixel_0 = (uint32 *)(Address_0 + i); + uint32 *Pixel_1 = (uint32 *)(Address_1 + i); + if (*Pixel_0 != 0x00000000) { + uint32 Temp = *Pixel_1; + *Pixel_1 = *Pixel_0; + *Pixel_0 = Temp; + } + i += sizeof(uint32); + } +} + + +static void +PaintTest(brush_info B, void *CacheBuffer, rectangle RenderRegion) +{ + for (int32 Y = RenderRegion.Min.y; Y < RenderRegion.Max.y; Y++) { + for (int32 X = RenderRegion.Min.x; X < RenderRegion.Max.x; X++) { + + uint32 Offset = Y*B.LayerPitch + X*B.SourceBytesPerPixel; + uint8 *LayerPixel = (uint8 *)CacheBuffer + Offset; + + uint32 *R_DestAddress = (uint32 *)(LayerPixel + B.LayerBits.ByteOffset * 0); + uint32 *G_DestAddress = (uint32 *)(LayerPixel + B.LayerBits.ByteOffset * 1); + uint32 *B_DestAddress = (uint32 *)(LayerPixel + B.LayerBits.ByteOffset * 2); + uint32 *A_DestAddress = (uint32 *)(LayerPixel + B.LayerBits.ByteOffset * 3); + + real32 R_Layer = (real32)(*R_DestAddress & B.LayerBits.MaskPixel) * B.LayerBits.Normalized; + real32 G_Layer = (real32)(*G_DestAddress & B.LayerBits.MaskPixel) * B.LayerBits.Normalized; + real32 B_Layer = (real32)(*B_DestAddress & B.LayerBits.MaskPixel) * B.LayerBits.Normalized; + real32 A_Layer = (real32)(*A_DestAddress & B.LayerBits.MaskPixel) * B.LayerBits.Normalized; + + int32 TrueX = (X - B.LayerBounds.Min.x) - B.ExtraX; + int32 TrueY = (Y - B.LayerBounds.Min.y) - B.ExtraY; + + uint8 *SourcePixel = (uint8 *)B.SourceBuffer + Offset; + Assert(B.SourceBytesPerPixel == 4); + bool32 IsEmpty = (*(uint32 *)SourcePixel == 0x00000000); + if (IsEmpty) + *(uint32 *)SourcePixel = 0x00010101; + + // Assert(TrueX >= 0 && TrueX < BrushLength); + // Assert(TrueY >= 0 && TrueY < BrushLength); + + uint8 *BrushPixel = (uint8 *)B.BrushRow + (TrueY * B.BrushPitch) + (TrueX * B.BytesPerPixel); + + real32 Brush_BitmapAlpha = (real32)((*(uint32 *)(BrushPixel + B.BrushBits.ByteOffset*3)) & B.BrushBits.MaskPixel) * B.BrushBits.Normalized; + + if (!Brush_BitmapAlpha) + continue; + + real32 BrushAlpha = Brush_BitmapAlpha; + real32 LayerAlpha = A_Layer; + + real32 A_Blend = 0; + real32 R_Blend = 0; + real32 G_Blend = 0; + real32 B_Blend = 0; + + if (!B.EraseMode) { + if (IsEmpty) { + R_Blend = B.R_Brush; + G_Blend = B.G_Brush; + B_Blend = B.B_Brush; + A_Blend = LayerAlpha + BrushAlpha; + } else { + R_Blend = B.R_Brush; + G_Blend = B.G_Brush; + B_Blend = B.B_Brush; + A_Blend = LayerAlpha + ((1.0f - LayerAlpha) * BrushAlpha); + real32 Blend = BrushAlpha; + // R_Blend = (R_Layer * (1.0f - Blend)) + (B.R_Brush * Blend); + // G_Blend = (G_Layer * (1.0f - Blend)) + (B.G_Brush * Blend); + // B_Blend = (B_Layer * (1.0f - Blend)) + (B.B_Brush * Blend); + } + // A_Blend = BrushAlpha; + } else { + A_Blend = A_Layer * (1.0f - BrushAlpha); + R_Blend = R_Layer; + G_Blend = G_Layer; + B_Blend = B_Layer; + } + + /* + R_Blend = (R_Brush * (1.0f - LayerAlpha)) + (R_Blend * LayerAlpha); + G_Blend = (G_Brush * (1.0f - LayerAlpha)) + (G_Blend * LayerAlpha); + B_Blend = (B_Brush * (1.0f - LayerAlpha)) + (B_Blend * LayerAlpha); + */ + + Assert(R_Blend <= 1.0f); + + uint32 R_Out = (uint32)(Normalize(R_Blend) * B.LayerBits.Bits); + uint32 G_Out = (uint32)(Normalize(G_Blend) * B.LayerBits.Bits); + uint32 B_Out = (uint32)(Normalize(B_Blend) * B.LayerBits.Bits); + uint32 A_Out = (uint32)(Normalize(A_Blend) * B.LayerBits.Bits); + + *R_DestAddress = (*R_DestAddress & ~B.LayerBits.MaskPixel) | R_Out; + *G_DestAddress = (*G_DestAddress & ~B.LayerBits.MaskPixel) | G_Out; + *B_DestAddress = (*B_DestAddress & ~B.LayerBits.MaskPixel) | B_Out; + *A_DestAddress = (*A_DestAddress & ~B.LayerBits.MaskPixel) | A_Out; + } + } +} + +#if ARM +#else +static void +PaintTest_AVX2(brush_info B, void *Buffer, rectangle RenderRegion) +{ + __m256 One = _mm256_set1_ps(1); + __m256 Zero = _mm256_set1_ps(0); + __m256 Eight = _mm256_set1_ps(8); + __m256i FF = _mm256_set1_epi32(0xFF); + __m256 R_Brush =_mm256_set1_ps(B.R_Brush); + __m256 G_Brush =_mm256_set1_ps(B.G_Brush); + __m256 B_Brush =_mm256_set1_ps(B.B_Brush); + __m256 A_Brush =_mm256_set1_ps(B.A_Brush); + __m256 Norm255 = _mm256_set1_ps(1/255.0f); + __m256 Real255 = _mm256_set1_ps(255.0f); + __m256 LayerBoundsMaxX = _mm256_set1_ps(B.SourceWidth); + + for (int32 Y = RenderRegion.Min.y; Y < RenderRegion.Max.y; Y++) { + __m256 PixelX = _mm256_setr_ps((real32)RenderRegion.Min.x, + (real32)RenderRegion.Min.x+1, + (real32)RenderRegion.Min.x+2, + (real32)RenderRegion.Min.x+3, + (real32)RenderRegion.Min.x+4, + (real32)RenderRegion.Min.x+5, + (real32)RenderRegion.Min.x+6, + (real32)RenderRegion.Min.x+7); + for (int32 X = RenderRegion.Min.x; X < RenderRegion.Max.x; X += 8) { + + __m256i TileBarrier = _mm256_cvtps_epi32(_mm256_cmp_ps(PixelX, LayerBoundsMaxX, 1)); + + uint32 Offset = Y*B.LayerPitch + X*B.SourceBytesPerPixel; + uint8 *LayerPixelAddress = (uint8 *)Buffer + Offset; + + __m256i LayerPixels = _mm256_loadu_si256((const __m256i *)LayerPixelAddress); + + __m256 R_Layer = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256( LayerPixels, FF)), Norm255); + __m256 G_Layer = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(LayerPixels, 8), FF)), Norm255); + __m256 B_Layer = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(LayerPixels, 16), FF)), Norm255); + __m256 A_Layer = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(LayerPixels, 24), FF)), Norm255); + + int32 TrueX = (X - B.LayerBounds.Min.x) - B.ExtraX; + int32 TrueY = (Y - B.LayerBounds.Min.y) - B.ExtraY; + uint8 *BrushPixelAddress = (uint8 *)B.BrushRow + (TrueY * B.BrushPitch) + (TrueX * B.BytesPerPixel); + __m256i BrushPixels = _mm256_loadu_si256((const __m256i *)BrushPixelAddress); + + __m256 Brush_BitmapAlpha = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(BrushPixels, 24), FF)), Norm255); + + __m256 A_BrushMultiplied = _mm256_mul_ps(Brush_BitmapAlpha, A_Brush); + + __m256 A_Blend = _mm256_add_ps(A_Layer, A_BrushMultiplied); + + __m256 R_Blend = _mm256_add_ps(_mm256_mul_ps(R_Layer, _mm256_sub_ps(One, A_BrushMultiplied)), _mm256_mul_ps(R_Brush, A_BrushMultiplied)); + __m256 G_Blend = _mm256_add_ps(_mm256_mul_ps(G_Layer, _mm256_sub_ps(One, A_BrushMultiplied)), _mm256_mul_ps(G_Brush, A_BrushMultiplied)); + __m256 B_Blend = _mm256_add_ps(_mm256_mul_ps(B_Layer, _mm256_sub_ps(One, A_BrushMultiplied)), _mm256_mul_ps(B_Brush, A_BrushMultiplied)); + + __m256i R_Out = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_max_ps(_mm256_min_ps(One, R_Blend), Zero), Real255)); + __m256i G_Out = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_max_ps(_mm256_min_ps(One, G_Blend), Zero), Real255)); + __m256i B_Out = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_max_ps(_mm256_min_ps(One, B_Blend), Zero), Real255)); + __m256i A_Out = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_max_ps(_mm256_min_ps(One, A_Blend), Zero), Real255)); + + __m256i OutputPixel = _mm256_or_si256( + _mm256_or_si256(R_Out, _mm256_slli_epi32(G_Out, 8)), + _mm256_or_si256(_mm256_slli_epi32(B_Out, 16), _mm256_slli_epi32(A_Out, 24))); + + // _mm256_storeu_si256((__m256i *)LayerPixelAddress, OutputPixel); + _mm256_maskstore_epi32((int *)LayerPixelAddress, TileBarrier, OutputPixel); + PixelX = _mm256_add_ps(PixelX, Eight); + } + } +} +#endif + +static void +RenderQueue_AddBrush(project_state *State, v2 LayerPos) +{ + State->Queue.Item[State->Queue.CurrentIdx].Pos = LayerPos; + State->Queue.Item[State->Queue.CurrentIdx].Type = 1; + State->Queue.CurrentIdx++; + State->Brush.PrevPos = LayerPos; +} + +static void +RenderQueue_AddBlit(project_state *State) +{ + State->Queue.Item[State->Queue.CurrentIdx].Type = 2; + State->Queue.CurrentIdx++; +} diff --git a/src/effects.cpp b/src/effects.cpp new file mode 100644 index 0000000..1aacddb --- /dev/null +++ b/src/effects.cpp @@ -0,0 +1,58 @@ +#include "effects_software.cpp" +#include "effects_gl.cpp" + +static void +Effect_DrawColor(real32 *Data, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, uint16 ShaderProgram) +{ + v4 Color = { Data[0], Data[1], Data[2], Data[3] }; + Effect_GL_DrawColor(Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram, Color); +} + +static void +Effect_GaussianBlur(real32 *Data, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, uint16 ShaderProgram) +{ + real32 Radius = Data[0]; + Effect_GL_GaussianBlur(Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram, Radius); +} + +static void +Effect_Curves_Init(block_effect *Effect, property_channel *Property) +{ + for (int i = 0; i < 5; i++) { + Property->Identifier = i; + Property++; + Property->Identifier = i; + Property++; + + Property->CurrentValue = 1.0f; + Property->Identifier = i; + Property++; + Property->CurrentValue = 1.0f; + Property->Identifier = i; + Property++; + + Effect->ExtraData[i] = 2; + } +} + +static void +Effect_Levels(real32 *Data, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, uint16 ShaderProgram) +{ + real32 Min = Data[0]; + real32 Mid = Data[1]; + real32 Max = Data[2]; + + v4 ColMin = *(v4 *)&Data[3]; + v4 ColMid = *(v4 *)&Data[3+4]; + v4 ColMax = *(v4 *)&Data[3+8]; + Effect_GL_Levels(Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram, Min, Mid, Max, ColMin, ColMid, ColMax); +} + +static void +Effect_Curves(real32 *Data, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, uint16 ShaderProgram) +{ + real32 PointCount = *Data; + v4 PointCount_Col = *(v4 *)(Data + 1); + v2 *PointData = (v2 *)(Data + 5); + Effect_Software_Curves(Width, Height, BytesPerPixel, EffectBitmapAddress, PointData, PointCount, PointCount_Col); +} diff --git a/src/effects_constructors.cpp b/src/effects_constructors.cpp new file mode 100644 index 0000000..406cb08 --- /dev/null +++ b/src/effects_constructors.cpp @@ -0,0 +1,117 @@ +static void +Effect_AddEntry(project_state *State, char *Name, char *ID, void (*func)(real32 *, int, int, int, void *, uint16), const char *GL_Shader, effect_display_type DisplayType = effect_display_type_standard) +{ + header_effect *Effect = &State->Effect[State->Playhead_Effect]; + Effect->Name = Name; + Effect->ID = ID; + Effect->func = func; + Effect->DisplayType = DisplayType; + Effect->PropertyStartIndex = State->Playhead_Property; + if (GL_Shader) { + Effect->GLShaderIndex = Effect_GL_InitShader(GL_Shader); + Effect->UseGL = true; + } +} + +static void +Effect_EndEntry(project_state *State) +{ + State->Playhead_Effect++; +} + +static void +Effect_AddProperty_Real(project_state *State, char *Name, real32 DefaultValue, real32 MinVal = -999999, real32 MaxVal = 999999, property_display_type DisplayType = property_display_type_standard) +{ + header_effect *Effect = &State->Effect[State->Playhead_Effect]; + Effect->Property_Count++; + header_property *Property = &State->Property[State->Playhead_Property++]; + Property->Name = Name; + Property->DefaultValue = DefaultValue; + Property->DisplayType = DisplayType; + Property->MinVal = MinVal; + Property->MaxVal = MaxVal; +} + +static void +Effect_AddProperty_Col(project_state *State, char *Name, v4 DefaultValue) +{ + Effect_AddProperty_Real(State, "r", DefaultValue.r, 0, 1, property_display_type_color); + Effect_AddProperty_Real(State, "g", DefaultValue.g, 0, 1, property_display_type_color); + Effect_AddProperty_Real(State, "b", DefaultValue.b, 0, 1, property_display_type_color); + Effect_AddProperty_Real(State, "a", DefaultValue.a, 0, 1, property_display_type_color); +} + +static void +Effect_AddProperty_Blendmode(project_state *State, char *Name, blend_mode DefaultValue) +{ + header_effect *Effect = &State->Effect[State->Playhead_Effect]; + Effect->Property_Count++; + header_property *Property = &State->Property[State->Playhead_Property++]; + Property->Name = Name; + Property->DefaultValue = DefaultValue; + Property->DisplayType = property_display_type_blendmode; +} + +static header_effect* +Effect_EntryFromID(project_state *State, char *ID) +{ + for (int i = 0; i < State->Playhead_Effect; i++) { + if (String_Compare(ID, State->Effect[i].ID, 8)) + return &State->Effect[i]; + } + Assert(0); + return &State->Effect[0]; +} + +static void +Effect_InitEntries(project_state *State) +{ + /* + */ + // Curves + /* + Effect_AddEntry(State, "Curves", "REALCRVS", &NULL, NULL, effect_display_type_curves); + Effect_AddProperty_Real(State, "Selected channel", 0.0f); + Effect_AddProperty_Real(State, "Number of points (main)", 2.0f); + Effect_AddProperty_Col(State, "Number of points (individual)", V4(2.0f)); + for (int i = 0; i < MAX_CURVESPOINTS*5/2; i++) { + v4 PointData = ((i % (MAX_CURVESPOINTS/2)) == 0) ? V4(0, 0, 1, 1) : V4(0); + Effect_AddProperty_Col(State, "PointData", PointData); + } + Effect_EndEntry(State); + */ + + // Solid color + Effect_AddEntry(State, "Solid color", "REALSCOL", &Effect_DrawColor, GLShader_SolidColor); + Effect_AddProperty_Col(State, "Color", V4(0.3f, 0.2f, 0.6f, 1.0f)); + Effect_EndEntry(State); + + // Gaussian blur + Effect_AddEntry(State, "Gaussian blur", "REALGBLR", &Effect_GaussianBlur, GLShader_GaussianBlur); + Effect_AddProperty_Real(State, "Radius", 1.0f, 0.0f, 200.0f); + Effect_EndEntry(State); + + // Curves + Effect_AddEntry(State, "Curves", "REALCRVS", &Effect_Curves, NULL, effect_display_type_curves); + for (int i = 0; i < MAX_PROPERTIES_PER_EFFECT; i++) { + Effect_AddProperty_Real(State, "point", 0.0f); + } + Effect_EndEntry(State); + + // Levels + Effect_AddEntry(State, "Levels", "REALLVLS", &Effect_Levels, GLShader_Levels, effect_display_type_levels); + // min/max is handled by the UI + Effect_AddProperty_Real(State, "All start point", 0.0f); + Effect_AddProperty_Real(State, "All mid point", 1.0f); + Effect_AddProperty_Real(State, "All end point", 1.0f); + Effect_AddProperty_Col(State, "Channel start point", V4(0.0f)); + Effect_AddProperty_Col(State, "Channel mid point", V4(1.0f)); + Effect_AddProperty_Col(State, "Channel end point", V4(1.0f)); + Effect_EndEntry(State); + /* + // Test gradient + Effect_AddEntry(State, "Test gradient", "REALTGRD", &Effect_TestGradient, NULL); + Effect_AddProperty_Col(State, "Color", V4(0.3f, 0.2f, 0.6f, 1.0f)); + Effect_EndEntry(State); + */ +} diff --git a/src/effects_gl.cpp b/src/effects_gl.cpp new file mode 100644 index 0000000..c3ff444 --- /dev/null +++ b/src/effects_gl.cpp @@ -0,0 +1,103 @@ +void GL_UpdateTexture(gl_effect_layer *Test, void *Data, uint16 Width, uint16 Height, uint16 BytesPerPixel, bool32 Multisample); +static uint16 Effect_GL_InitShader(const char *Effect); +static void GL_BindDefaultVertexArrays(); + +void Effect_GL_Start(gl_effect_layer *Test, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, uint16 ShaderProgram) +{ + GL_UpdateTexture(Test, EffectBitmapAddress, Width, Height, BytesPerPixel, 0); + + GL_BindDefaultVertexArrays(); + + glUseProgram(ShaderProgram); + + glBindFramebuffer(GL_FRAMEBUFFER, Test->FramebufferObject); + glBindRenderbuffer(GL_RENDERBUFFER, Test->Color_Renderbuffer); + + glBindTexture(GL_TEXTURE_2D, Test->Texture); + int ByteFlag = (BytesPerPixel == 4) ? GL_RGBA : GL_RGBA16; + int ByteFlag2 = (BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT; + glTexImage2D(GL_TEXTURE_2D, 0, ByteFlag, Width, Height, 0, GL_RGBA, + ByteFlag2, EffectBitmapAddress); +} + +void Effect_GL_DrawColor(int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, + uint16 ShaderProgram, v4 Color) +{ + gl_effect_layer Test = {}; + + int ByteFlag = (BytesPerPixel == 4) ? GL_RGBA : GL_RGBA16; + int ByteFlag2 = (BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT; + Effect_GL_Start(&Test, Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram); + + int Uniform = glGetUniformLocation(ShaderProgram, "Color"); + glUniform4f(Uniform, Color.r, Color.g, Color.b, Color.a); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + glReadPixels(0, 0, Width, Height, GL_RGBA, ByteFlag2, EffectBitmapAddress); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + GL_DeleteHWBuffer(&Test); +} + +void Effect_GL_GaussianBlur(int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, + uint16 ShaderProgram, real32 Radius) +{ + gl_effect_layer Test = {}; + + int ByteFlag = (BytesPerPixel == 4) ? GL_RGBA : GL_RGBA16; + int ByteFlag2 = (BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT; + Effect_GL_Start(&Test, Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram); + + // horizontal pass + int Uniform = glGetUniformLocation(ShaderProgram, "Radius"); + glUniform1f(Uniform, Radius + 1.60f); + Uniform = glGetUniformLocation(ShaderProgram, "Direction"); + glUniform2f(Uniform, 1.0f, 0.0f); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + glReadPixels(0, 0, Width, Height, GL_RGBA, ByteFlag2, EffectBitmapAddress); + // + + // vertical pass + glTexImage2D(GL_TEXTURE_2D, 0, ByteFlag, Width, Height, 0, GL_RGBA, + ByteFlag2, EffectBitmapAddress); + + Radius = glGetUniformLocation(ShaderProgram, "Direction"); + glUniform2f(Uniform, 0.0f, 1.0f); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + glReadPixels(0, 0, Width, Height, GL_RGBA, ByteFlag2, EffectBitmapAddress); + // + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + GL_DeleteHWBuffer(&Test); +} + +void Effect_GL_Levels(int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, + uint16 ShaderProgram, real32 Min, real32 Mid, real32 Max, v4 ColMin, v4 ColMid, v4 ColMax) +{ + gl_effect_layer Test = {}; + + int ByteFlag = (BytesPerPixel == 4) ? GL_RGBA : GL_RGBA16; + int ByteFlag2 = (BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT; + Effect_GL_Start(&Test, Width, Height, BytesPerPixel, EffectBitmapAddress, ShaderProgram); + + int Uniform = glGetUniformLocation(ShaderProgram, "Start"); + glUniform1f(Uniform, Min); + Uniform = glGetUniformLocation(ShaderProgram, "Mid"); + glUniform1f(Uniform, Mid); + Uniform = glGetUniformLocation(ShaderProgram, "End"); + glUniform1f(Uniform, Max); + Uniform = glGetUniformLocation(ShaderProgram, "StartCol"); + glUniform4f(Uniform, ColMin.r, ColMin.g, ColMin.b, ColMin.a); + Uniform = glGetUniformLocation(ShaderProgram, "MidCol"); + glUniform4f(Uniform, ColMid.r, ColMid.g, ColMid.b, ColMid.a); + Uniform = glGetUniformLocation(ShaderProgram, "EndCol"); + glUniform4f(Uniform, ColMax.r, ColMax.g, ColMax.b, ColMax.a); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + glReadPixels(0, 0, Width, Height, GL_RGBA, ByteFlag2, EffectBitmapAddress); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + GL_DeleteHWBuffer(&Test); +} diff --git a/src/effects_gl_shader.cpp b/src/effects_gl_shader.cpp new file mode 100644 index 0000000..b2e1fc1 --- /dev/null +++ b/src/effects_gl_shader.cpp @@ -0,0 +1,57 @@ +const char *GLShader_Levels = "#version 330 core\n" +"out vec4 FragColor;\n" +"in vec2 TexCoord;\n" +"uniform float Start;\n" +"uniform float Mid;\n" +"uniform float End;\n" +"uniform vec4 StartCol;\n" +"uniform vec4 MidCol;\n" +"uniform vec4 EndCol;\n" +"uniform sampler2D Texture;\n" +"void main()\n" +"{\n" +"vec4 OutCol = texture(Texture, TexCoord);\n" +// individual channels +"vec4 ColorI = pow(OutCol, MidCol);\n" +"vec4 ValI = 1.0f / (EndCol - StartCol) * (ColorI - StartCol);\n" +// global channel (doesn't affect alpha) +"vec4 ColorG = pow(ValI, vec4(Mid));\n" +"vec4 ValG = 1.0f / (End - Start) * (ColorG - Start);\n" +"ValG = vec4(ValG.rgb, ValI.a);\n" +"FragColor = clamp(ValG, 0.0f, 1.0f);\n" +"}\0"; + +const char *GLShader_SolidColor = "#version 330 core\n" +"out vec4 FragColor;\n" +"in vec2 TexCoord;\n" +"uniform vec4 Color;\n" +"uniform sampler2D Texture;\n" +"void main()\n" +"{\n" +"vec4 OutCol = texture(Texture, TexCoord);\n" +"FragColor = Color*OutCol;\n" +"}\0"; + +const char *GLShader_GaussianBlur = "#version 330 core\n" +"uniform float Radius;\n" +"uniform vec2 Direction;\n" +"uniform sampler2D Texture;\n" +"out vec4 FragColor;\n" +"in vec2 TexCoord;\n" +"\n" +"vec4 blur(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {\n" +" vec4 color = vec4(0.0f);\n" +" float Omega = Radius / 3;\n" +" float Divisor = 2*Omega*Omega;\n" +" float A2 = 1.0f / (Omega * sqrt(2*3.141592));\n" +" for (float Span = -round(Radius); Span < round(Radius); Span++) {\n" +" float Dividend = -Span * Span;\n" +" float Multiplier = A2 * exp(Dividend/Divisor);\n" +" vec2 Dir = Span*direction;\n" +" color += texture(image, uv + (Dir / resolution)) * Multiplier;\n" +" }\n" +" return color;\n" +"}\n" +"void main(void) {\n" +" FragColor = blur(Texture, TexCoord, vec2(1280, 720), Direction);\n" +"}\0"; diff --git a/src/effects_software.cpp b/src/effects_software.cpp new file mode 100644 index 0000000..2fde6b9 --- /dev/null +++ b/src/effects_software.cpp @@ -0,0 +1,209 @@ + +static void +Effect_Software_DrawColor(int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, v4 Color, blend_mode BlendMode) +{ + render_byte_info Bits = Bitmap_ByteInfo(BytesPerPixel); + rectangle RenderRegion = {0, 0, Width, Height}; + transform_info T; + T.BlendMode = BlendMode; + for (int32 Y = RenderRegion.Min.y; Y < RenderRegion.Max.y; Y++) { + for (int32 X = RenderRegion.Min.x; X < RenderRegion.Max.x; X++) { + uint32 Offset = Y*Width*BytesPerPixel + X*BytesPerPixel; + uint8 *LayerPixel = (uint8 *)EffectBitmapAddress + Offset; + + uint32 *R_DestAddress = (uint32 *)(LayerPixel + Bits.ByteOffset * 0); + uint32 *G_DestAddress = (uint32 *)(LayerPixel + Bits.ByteOffset * 1); + uint32 *B_DestAddress = (uint32 *)(LayerPixel + Bits.ByteOffset * 2); + uint32 *A_DestAddress = (uint32 *)(LayerPixel + Bits.ByteOffset * 3); + + real32 R_Dest = (real32)(*R_DestAddress & Bits.MaskPixel) * Bits.Normalized; + real32 G_Dest = (real32)(*G_DestAddress & Bits.MaskPixel) * Bits.Normalized; + real32 B_Dest = (real32)(*B_DestAddress & Bits.MaskPixel) * Bits.Normalized; + real32 A_Dest = (real32)(*A_DestAddress & Bits.MaskPixel) * Bits.Normalized; + + real32 R_Col = Color.r; + real32 G_Col = Color.g; + real32 B_Col = Color.b; + real32 A_Col = Color.a; + + real32 LayerAlpha = A_Col * 1; + + real32 R_Blend = R_Col; + real32 G_Blend = G_Col; + real32 B_Blend = B_Col; + real32 A_Blend = A_Col; + + Fallback_Blend(); + + uint32 R_Out = (uint32)(Normalize(R_Blend) * Bits.Bits); + uint32 G_Out = (uint32)(Normalize(G_Blend) * Bits.Bits); + uint32 B_Out = (uint32)(Normalize(B_Blend) * Bits.Bits); + uint32 A_Out = (uint32)(Normalize(A_Blend) * Bits.Bits); + + *R_DestAddress = (*R_DestAddress & ~Bits.MaskPixel) | R_Out; + *G_DestAddress = (*G_DestAddress & ~Bits.MaskPixel) | G_Out; + *B_DestAddress = (*B_DestAddress & ~Bits.MaskPixel) | B_Out; + *A_DestAddress = (*A_DestAddress & ~Bits.MaskPixel) | A_Out; + } + } +} +static void +CurvesSolver(real32 *LUT, v2 Point_P1, v2 Point_P2, v2 m1, v2 m2, int i) +{ + + real32 Precision = ((real32)1 / 256) * 0.1; + real32 Point_Span = Point_P2.x - Point_P1.x; + v2 Cache[256]; + + if (i == 0) { + // Don't know how to fix this, so I'm just gonna linear interpolate + // until I try quadratic solving. + real32 Count_Start = (Point_P1.x * 256); + real32 Count_End = (Point_P2.x * 256); + real32 Count_Total = Count_End - Count_Start; + real32 Width = Point_P2.x - Point_P1.x; + real32 Height = Point_P2.y - Point_P1.y; + real32 Count = Count_Start; + real32 t = 1; + while (Count < Count_End) { + LUT[(uint32)Count] = Normalize(Point_P1.y + (Height*((Count-Count_Start)/(Count_End - (Count_End - 256)))/Width)); + Count++; + } + } else { + real32 Count_Start = (Point_P1.x * 256); + real32 Count_End = (Point_P2.x * 256); + real32 Count_Total = Count_End - Count_Start; + real32 Count = Count_Start; + real32 t = 0; + int Timeout = 0; + + // This solver actually works kinda decently when the graph isn't that + // complex, taking less than 10 iterations per LUT value. It fails + // towards the edges and with harsh curves, going into the hundreds. The + // 1000 condition should only be hit when the solver is locked, which can + // happen when two points are close together on X. + + while (Count < Count_End) { + + real32 c = 2*t*t*t - 3*t*t; + real32 c0 = c + 1; + real32 c1 = t*t*t - 2*t*t + t; + real32 c2 = -c; + real32 c3 = t*t*t - t*t; + + v2 Point = (c0 * Point_P1) + (c1 * m1) + (c2 * Point_P2) + (c3 * m2); + + real32 TargetX = Count / 256; + + if (Timeout == 1000) { + Point.x = TargetX; + printf("Solve between %.1f and %.1f reached 1000 iterations at %.f!\n", Count_Start, Count_End, Count); + } + + // Only record the value if it's within a certain precision. + + if (Point.x <= TargetX - Precision || + Point.x >= TargetX + Precision) { + t = t * TargetX / Point.x; + Timeout++; + } else { + if (Point.y > 1.0f) { + LUT[(uint32)Count] = 1.0f; + } else if (Point.y < 0.0f) + LUT[(uint32)Count] = 0.0f; + else { + LUT[(uint32)Count] = Point.y; + } + t += (Point_Span / Count_Total); + Count++; + Timeout = 0; + } + } + } +} + +static void +Effect_Software_Curves(int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, v2 *PointData, real32 PointCount, v4 PointCount_Col) +{ + real32 LUT[5][256] = {}; + + for (int a = 0; a < 5; a++) { + + int Num = (a == 0) ? (int)PointCount : (int)PointCount_Col.E[a-1]; + v2 *CurvePoint = PointData + (MAX_PROPERTIES_PER_EFFECT / 5 * a); + + for (int i = 0; i < Num; i++) { + v2 Point_P1 = CurvePoint[i]; + v2 Point_P2 = CurvePoint[i + 1]; + v2 Point_P0 = (i != 0) ? CurvePoint[i - 1] : V2(0, 0); + v2 Point_P3 = (i != (Num - 2)) ? CurvePoint[i + 2] : V2(1, 1); + + v2 m1 = (Point_P2 - Point_P0) / (2 * Tau); + v2 m2 = (Point_P3 - Point_P1) / (2 * Tau); + + CurvesSolver(LUT[a], Point_P1, Point_P2, m1, m2, i); + } + + if (CurvePoint[0].x > 0.0f) { + real32 Count_Start = 0; + real32 Count_End = (CurvePoint[0].x * 255); + real32 Count = Count_Start; + while (Count < Count_End) { + LUT[a][(uint32)Count] = LUT[a][(uint32)Count_End]; + Count++; + } + } + + if (CurvePoint[Num-1].x < 1.0f) { + real32 Count_Start = (CurvePoint[Num-1].x * 255) - 0.5; + real32 Count_End = 255; + real32 Count = Count_Start; + while (Count < Count_End) { + LUT[a][(uint32)Count] = LUT[a][(uint32)Count_Start]; + Count++; + } + } + + for (int i = 0; i < Num; i++) { + if (CurvePoint[i].y == 1.0f) + LUT[a][255] = 1.0f; + } + } + + + uint64 Size = Width*Height; + int i = 0; + Assert(BytesPerPixel == 4); + while (i < Size) { + uint32 *Pixel = (uint32 *)EffectBitmapAddress + i; + + uint8 A = (*Pixel >> 24); + uint8 B = (*Pixel >> 16); + uint8 G = (*Pixel >> 8); + uint8 R = (*Pixel >> 0); + +#if 1 + real32 R_Lookup = LUT[1][R]; + real32 G_Lookup = LUT[2][G]; + real32 B_Lookup = LUT[3][B]; + real32 A_Lookup = LUT[4][A]; + + real32 R_Lookup_All = LUT[0][(uint32)(R_Lookup*255)]; + real32 G_Lookup_All = LUT[0][(uint32)(G_Lookup*255)]; + real32 B_Lookup_All = LUT[0][(uint32)(B_Lookup*255)]; +#else + real32 R_Lookup_All = LUT[0][(uint32)(t.r)]; + real32 G_Lookup_All = LUT[0][(uint32)(t.g)]; + real32 B_Lookup_All = LUT[0][(uint32)(t.b)]; +#endif + + + uint32 Result = (((uint32)((A_Lookup * 255.0f) + 0.5) << 24) | + ((uint32)((B_Lookup_All * 255.0f) + 0.5) << 16) | + ((uint32)((G_Lookup_All * 255.0f) + 0.5) << 8) | + ((uint32)((R_Lookup_All * 255.0f) + 0.5) << 0)); + + *Pixel = Result; + i++; + } +} diff --git a/src/ffmpeg_backend.cpp b/src/ffmpeg_backend.cpp new file mode 100644 index 0000000..f4c4492 --- /dev/null +++ b/src/ffmpeg_backend.cpp @@ -0,0 +1,480 @@ +// workaround to make libav error printing work +#ifdef av_err2str +#undef av_err2str +#include <string> +av_always_inline std::string av_err2string(int errnum) { + char str[AV_ERROR_MAX_STRING_SIZE]; + return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum); +} +#define av_err2str(err) av_err2string(err).c_str() +#endif // av_err2str + +bool32 AV_TryFrame(av_info *AV, AVCodecContext *CodecContext, int32 *err, bool32 *EndOfFile, int StreamIndex = -1) +{ + *err = av_read_frame(AV->FileFormatContext, AV->Packet); + bool32 CheckForStream = (StreamIndex != -1) ? (AV->Packet->stream_index == StreamIndex) : 1; + if (!(*err >= 0 && CheckForStream)) { + av_packet_unref(AV->Packet); + return 0; + } + + if (*err < 0) + *err = avcodec_send_packet(CodecContext, AV->Packet); + else { + *err = avcodec_send_packet(CodecContext, AV->Packet); + } + av_packet_unref(AV->Packet); + + if (*err == AVERROR_EOF) + { + *err = 0; + *EndOfFile = true; + av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(CodecContext); + return 0; + } + + if (*err < 0) + { + fprintf(stderr, "Libav *error: (%s)\n", av_err2str(*err)); + Assert(0); + } + + while (*err >= 0) { + *err = avcodec_receive_frame(CodecContext, AV->Frame); + if (*err == AVERROR_EOF) { + *EndOfFile = true; + av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(CodecContext); + *err = 0; + break; + } else if (*err == AVERROR(EAGAIN)) { + *err = 0; + break; + } else if (*err < 0) { + Assert(0); + } + return 1; + } + return 0; +} + +void AV_IsFileSupported(char *filename, bool32 *IsVideo, bool32 *HasAudio) +{ + int32 err = 0; + + AVFormatContext *temp = avformat_alloc_context(); + err = avformat_open_input(&temp, filename, NULL, NULL);; + + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + avformat_free_context(temp); + return; + } + + err = avformat_find_stream_info(temp, NULL); + + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + avformat_free_context(temp); + return; + } + + for (uint32 i = 0; i < temp->nb_streams; i++) + { + AVCodecParameters *LocalCodecParameters = NULL; + LocalCodecParameters = temp->streams[i]->codecpar; + const AVCodec *codec = avcodec_find_decoder(LocalCodecParameters->codec_id); + if (LocalCodecParameters->codec_type == AVMEDIA_TYPE_AUDIO) { + if (codec) { + *HasAudio = true; + } else { + fprintf(stderr, "Codec not found.\n"); + } + } + if (LocalCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO) { + if (temp->streams[i]->duration == 1) { + fprintf(stderr, "TODO(fox): Make single-frame FFmpeg work!\n"); + } else if (codec) { + *IsVideo = true; + } else { + fprintf(stderr, "Codec not found.\n"); + } + } + } + + avformat_free_context(temp); +} + +void AV_Dealloc(av_info *AV) +{ + Assert(AV); + avformat_free_context(AV->FileFormatContext); + avcodec_free_context(&AV->Video.CodecContext); + avcodec_free_context(&AV->Audio.CodecContext); + av_packet_free(&AV->Packet); + av_frame_free(&AV->Frame); +}; + +void AV_InitStream(av_stream_info *Stream) +{ + int32 err = 0; + Stream->Codec = avcodec_find_decoder(Stream->CodecParameters->codec_id); + + if (!Stream->Codec) { + printf("Libav error: Suitable decoder could not be identified for codec.\n"); + } + + Stream->CodecContext = avcodec_alloc_context3(Stream->Codec); + if (!Stream->CodecContext) { + printf("Libav error: Decoder context allocation failed."); + } + err = avcodec_parameters_to_context(Stream->CodecContext, Stream->CodecParameters); + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + } + avcodec_open2(Stream->CodecContext, Stream->Codec, NULL); + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + } + +} + +// The duration isn't always reported in AVStream, but seeking towards the end +// and advancing until we hit EOF seems to be accurate. +void AV_GetDuration(av_info *AV, av_stream_info *Stream, uint64 *Duration, uint32 *FrameCount) +{ + if (Stream->Stream->duration > 0) { + *Duration = Stream->Stream->duration; + } else { + avformat_seek_file(AV->FileFormatContext, -1, INT64_MIN, 10000000000, INT64_MAX, 0); + + int32 err = 0; + bool32 EndOfFile = 0; + uint64 TestDuration = 0; + while (err >= 0) { + if (AV_TryFrame(AV, Stream->CodecContext, &err, &EndOfFile, Stream->Index)) + { + TestDuration = AV->Frame->pts; + } + av_frame_unref(AV->Frame); + if (EndOfFile) + break; + } + *Duration = TestDuration; + } + if (Stream->Stream->nb_frames > 0) { + *FrameCount = Stream->Stream->nb_frames; + } else if (AV->Video.CodecContext) { + Assert(AV->FileFormatContext->duration > 0); + int TotalSeconds = AV->FileFormatContext->duration / 1000000LL; + *FrameCount = (int)(TotalSeconds * (real32)AV->Video.Stream->r_frame_rate.num / AV->Video.Stream->r_frame_rate.den); + } +} + + +void AV_Init(block_source *Source, av_info *AV, memory *Memory) +{ + int32 err = 0; + + // enum AVHWDeviceType type; + // while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) + // printf("%s\n", av_hwdevice_get_type_name(type)); + + // The two calls below theoretically shouldn't fail since we already tested them in IsFileSupported. + + AV->FileFormatContext = avformat_alloc_context(); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); + err = avformat_open_input(&AV->FileFormatContext, String->Char, NULL, NULL);; + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + Assert(0); + } + + err = avformat_find_stream_info(AV->FileFormatContext, NULL); + + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + Assert(0); + } + + for (uint32 i = 0; i < AV->FileFormatContext->nb_streams; i++) + { + AVCodecParameters *LocalCodecParameters = NULL; + LocalCodecParameters = AV->FileFormatContext->streams[i]->codecpar; + if (LocalCodecParameters->codec_type == AVMEDIA_TYPE_AUDIO) { + AV->Audio.Index = i; + AV->Audio.CodecParameters = LocalCodecParameters; + AV->Audio.Stream = AV->FileFormatContext->streams[i]; + } + if (LocalCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO) { + AV->Video.Index = i; + AV->Video.CodecParameters = LocalCodecParameters; + AV->Video.Stream = AV->FileFormatContext->streams[i]; + } + if (AV->Video.Stream && AV->Audio.Stream) + break; + } + + if (!AV->Video.CodecParameters && !AV->Audio.CodecParameters) { + printf("Libav error: No tracks found."); + } + + if (AV->Video.CodecParameters) + AV_InitStream(&AV->Video); + if (AV->Audio.CodecParameters) + AV_InitStream(&AV->Audio); + + AV->Packet = av_packet_alloc(); + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + } + AV->Frame = av_frame_alloc(); + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + } + + if (AV->Video.CodecParameters) { + Source->BytesPerPixel = 4; + Source->Width = AV->Video.CodecContext->width; + Source->Height = AV->Video.CodecContext->height; + Source->FPS = (real32)AV->Video.Stream->r_frame_rate.num / AV->Video.Stream->r_frame_rate.den; + AV_GetDuration(AV, &AV->Video, &AV->PTSDuration, &AV->FrameCount); + AV->LastFrameRendered = -1; + av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(AV->Video.CodecContext); + } else { + AV_GetDuration(AV, &AV->Audio, &AV->PTSDuration, &AV->FrameCount); + av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(AV->Audio.CodecContext); + } +}; + +uint32 AV_AudioTest(av_info *AV, void *Data, uint32 Size) +{ + AVChannelLayout ChannelLayout = {}; + av_channel_layout_default(&ChannelLayout, 2); + + av_stream_info *Stream = &AV->Audio; + Assert(Stream->CodecContext->ch_layout.nb_channels == 2); + + int32 err = 0; + bool32 EndOfFile = 0; + while (err >= 0) { + if (AV_TryFrame(AV, Stream->CodecContext, &err, &EndOfFile, Stream->Index)) + { + if (AV->Frame->pts < Stream->Stream->start_time) { + av_frame_unref(AV->Frame); + continue; + } + // NOTE(fox): The docs say Frame->format corresponds to AVSampleFormat, but ffplay doesn't need the cast. + SwrContext *Swr = swr_alloc(); + swr_alloc_set_opts2(&Swr, &ChannelLayout, AV_SAMPLE_FMT_S16, 48000, + &AV->Frame->ch_layout, (AVSampleFormat)AV->Frame->format, AV->Frame->sample_rate, + 0, NULL); + swr_init(Swr); + + uint8 **Out = (uint8 **)&Data; + const uint8 **In = (const uint8_t **)AV->Frame->extended_data; + int len = swr_convert(Swr, Out, Size, In, AV->Frame->nb_samples); + if (len < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(len)); + av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n"); + Assert(0); + } + /* + int BytesPerSample = av_get_bytes_per_sample(Stream->CodecContext->sample_fmt); + Assert(BytesPerSample == 2 || BytesPerSample == 4); + int Offset = (BytesPerSample == 4) ? 2 : 1; + for (int i = 0; i < (Stream->Frame->nb_samples * 2); i+=2) { + uint16 *Channel_L = (uint16 *)Data + i + 0; + uint16 *Channel_R = (uint16 *)Data + i + 1; + *Channel_L = (*(uint16 *)(Stream->Frame->data[0] + BytesPerSample*i)) / Offset; + *Channel_R = (*(uint16 *)(Stream->Frame->data[1] + BytesPerSample*i)) / Offset; + int aa = 0; + } + */ + return len; + } + av_frame_unref(AV->Frame); + } + return 0; +} + +void AV_SeekAudio(av_info *AV, uint32 FPS, int32 FrameToSeek) +{ + Assert(FrameToSeek > -1) + int64 SeekSeconds = (int64)(FrameToSeek / (int32)(FPS + 0.5f) * AV_TIME_BASE); + av_seek_frame(AV->FileFormatContext, -1, SeekSeconds, AVSEEK_FLAG_BACKWARD); + + int64 SeekPTS = (int64)(((real64)FrameToSeek / AV->FrameCount) * AV->PTSDuration); + + //int64 AveragePTS = (AV->Video.CodecContext) ? AV->PTSDuration / AV->FrameCount : + + int32 err = 0; + bool32 EndOfFile = 0; + while (err >= 0) { + if (AV_TryFrame(AV, AV->Audio.CodecContext, &err, &EndOfFile)) + { + int a = 0; + } + } + AV_TryFrame(AV, AV->Video.CodecContext, &err, &EndOfFile); + + int64 AveragePTS = AV->PTSDuration / AV->FrameCount; + + while (err >= 0) { + if (AV_TryFrame(AV, AV->Video.CodecContext, &err, &EndOfFile, AV->Video.Index)) + { + // The first frame that gets loaded isn't always the actual + // first frame, so we need to check until it's correct. + if (FrameToSeek == 0 && AV->Frame->pts < AV->Video.Stream->start_time) { + av_frame_unref(AV->Frame); + // printf("NON-START: avg: %li, real pts: %li", SeekPTS, AV->VideoFrame->pts); + continue; + } + + int64 Difference = AV->Frame->pts - SeekPTS; + if (abs(Difference) < AveragePTS) + { + return; + } + } + } +} + + +void AV_LoadVideoFrame(memory *Memory, block_source *Source, av_info *AV, int32 FrameToSeek, void *Buffer) +{ + int32 *LastFrameRendered = &AV->LastFrameRendered; + + int32 err = 0; + + Assert(FrameToSeek > -1) + + // NOTE(fox): The decoder automatically advances to the next frame, so we + // don't need to call av_seek_frame under normal playback. + // This function only seeks to the nearest "keyframe." + + if (*LastFrameRendered != FrameToSeek - 1) { + int64 SeekSeconds = (int64)(FrameToSeek / (int32)(Source->FPS + 0.5f) * AV_TIME_BASE); + av_seek_frame(AV->FileFormatContext, -1, SeekSeconds, AVSEEK_FLAG_BACKWARD); + printf("Seek activated\n"); + } else if (*LastFrameRendered < 0) { + av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); + } + + *LastFrameRendered = FrameToSeek; + + int64 SeekPTS = (int64)(((real64)FrameToSeek / AV->FrameCount) * AV->PTSDuration); + int64 AveragePTS = AV->PTSDuration / AV->FrameCount; + + bool32 EndOfFile = 0; + while (err >= 0) { + if (AV_TryFrame(AV, AV->Video.CodecContext, &err, &EndOfFile, AV->Video.Index)) + { + // The first frame that gets loaded isn't always the actual + // first frame, so we need to check until it's correct. + if (FrameToSeek == 0 && AV->Frame->pts < AV->Video.Stream->start_time) { + av_frame_unref(AV->Frame); + // printf("NON-START: avg: %li, real pts: %li", SeekPTS, AV->VideoFrame->pts); + continue; + } + + int64 Difference = AV->Frame->pts - SeekPTS; + if (abs(Difference) < AveragePTS) + { + if (AV->PreviousPTS == -1) { + AV->PreviousPTS = AV->Frame->pts; + // printf("avg: %li, real pts: %li, difference: %li\n", SeekPTS, AV->Frame->pts, Difference); + } else { + // printf("avg: %li, real pts: %li, difference: %li difference from last pts: %li\n", SeekPTS, AV->Frame->pts, AV->Frame->pts - SeekPTS, AV->Frame->pts - AV->PreviousPTS); + AV->PreviousPTS = AV->Frame->pts; + } + + uint16 Width = Source->Width; + uint16 Height = Source->Height; + uint16 BytesPerPixel = Source->BytesPerPixel; + int32 Pitch = Width*BytesPerPixel; + + int out_linesize[4] = { Pitch, Pitch, Pitch, Pitch }; + uint8 *dst_data[4] = { (uint8 *)Buffer, (uint8 *)Buffer + Width*Height*BytesPerPixel, + (uint8 *)Buffer + Width*Height*BytesPerPixel*2, (uint8 *)Buffer + Width*Height*BytesPerPixel*3 }; + + // NOTE(fox): This function will be replaced in the future. + AV->RGBContext = sws_getContext(AV->Frame->width, AV->Frame->height, (AVPixelFormat)AV->Frame->format, + AV->Frame->width, AV->Frame->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, + NULL, NULL, NULL); + + if(!AV->RGBContext) { + printf("Libav error: SwsContext creation failed."); + } + + sws_scale(AV->RGBContext, AV->Frame->data, AV->Frame->linesize, 0, AV->Frame->height, + dst_data, out_linesize); + + av_frame_unref(AV->Frame); + return; + } + else + { + // If this gets printed when not seeking, a frame has been skipped! + // printf("FRAME SKIP: avg: %li, real pts: %li, difference: %li\n", SeekPTS, AV->VideoFrame->pts, Difference); + } + av_frame_unref(AV->Frame); + } + } + Assert(0); +} + +#if 0 + +cached_bitmap * AV_LoadStill(block_source *Source, layer_bitmap_info *BitmapInfo, memory *Memory) +{ + av_info *AV = (av_info *)BitmapInfo->AVInfo; + int32 *CurrentlyRenderedFrame = &BitmapInfo->CurrentFrame; + + int32 err = 0; + + *CurrentlyRenderedFrame = 0; + + av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_FRAME); + + while (err >= 0) { + if (AV_TryFrame(AV, &err)) + { + uint16 Width = Source->Width; + uint16 Height = Source->Height; + uint16 BytesPerPixel = Source->BytesPerPixel; + int32 Pitch = Width*BytesPerPixel; + + cached_bitmap *Bitmap = Memory_RollingBitmap(Memory, Source, 0); + void *Buffer = Bitmap->Data; + + int out_linesize[4] = { Pitch, Pitch, Pitch, Pitch }; + uint8 *dst_data[4] = { (uint8 *)Buffer, (uint8 *)Buffer + Width*Height*BytesPerPixel, + (uint8 *)Buffer + Width*Height*BytesPerPixel*2, (uint8 *)Buffer + Width*Height*BytesPerPixel*3 }; + + // NOTE(fox): This function will be replaced in the future. + AV->RGBContext = sws_getContext(AV->VideoFrame->width, AV->VideoFrame->height, (AVPixelFormat)AV->VideoFrame->format, + AV->VideoFrame->width, AV->VideoFrame->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, + NULL, NULL, NULL); + + if(!AV->RGBContext) { + printf("Libav error: SwsContext creation failed."); + } + + sws_scale(AV->RGBContext, AV->VideoFrame->data, AV->VideoFrame->linesize, 0, AV->VideoFrame->height, + dst_data, out_linesize); + + av_frame_unref(AV->VideoFrame); + + Assert(BitmapInfo->BitmapBuffer); + return Bitmap; + } + av_frame_unref(AV->VideoFrame); + } + return 0; +} + +#endif diff --git a/src/gl_calls.cpp b/src/gl_calls.cpp new file mode 100644 index 0000000..1cb408a --- /dev/null +++ b/src/gl_calls.cpp @@ -0,0 +1,268 @@ +#include "gl_calls.h" + +const char *DefaultVertexShaderSource = "#version 330 core\n" +"layout (location = 0) in vec3 aPos;\n" +"layout (location = 1) in vec2 aTexCoord;\n" +"out vec2 TexCoord;\n" +"uniform int VertexMode;\n" +"uniform vec3 CompDimensions;\n" +"void main()\n" +"{\n" +" if (VertexMode == 0) {\n" +" gl_Position = vec4(aPos, 1.0);\n" +"} else {\n" +" gl_Position = vec4(vec2(aPos.x / CompDimensions.x, aPos.y / CompDimensions.y) * 2 - 1.0f, 0.0f, 1.0);\n" +"}\n" +" TexCoord = aTexCoord;\n" +"}\0"; +const char *DefaultFragmentShaderSource = "#version 330 core\n" +"out vec4 FragColor;\n" +"in vec2 TexCoord;\n" +"uniform sampler2D Texture;\n" +"uniform int FragmentMode;\n" +"void main()\n" +"{\n" +"vec4 Col = texture(Texture, TexCoord);\n" +" if (FragmentMode == 0) {\n" +" FragColor = Col;\n" +"} else {\n" +" FragColor = vec4(vec3(1.0f), Col.a);\n" +"}\n" +"}\0"; + +static void GL_InitDefaultShader() { + DefaultVertexShader = glCreateShader(GL_VERTEX_SHADER); + + glShaderSource(DefaultVertexShader, 1, &DefaultVertexShaderSource, NULL); + glCompileShader(DefaultVertexShader); + + int success; + char infoLog[512]; + glGetShaderiv(DefaultVertexShader, GL_COMPILE_STATUS, &success); + + if(!success) + { + glGetShaderInfoLog(DefaultVertexShader, 512, NULL, infoLog); + printf("Vertex shader fail:\n %s", infoLog); + } + + uint32 DefaultFragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + glShaderSource(DefaultFragmentShader, 1, &DefaultFragmentShaderSource, NULL); + glCompileShader(DefaultFragmentShader); + + glGetShaderiv(DefaultFragmentShader, GL_COMPILE_STATUS, &success); + + if(!success) + { + glGetShaderInfoLog(DefaultFragmentShader, 512, NULL, infoLog); + printf("Fragment shader fail:\n %s", infoLog); + } + + // Shader programs link both types of shaders together. + DefaultShaderProgram = glCreateProgram(); + + glAttachShader(DefaultShaderProgram, DefaultVertexShader); + glAttachShader(DefaultShaderProgram, DefaultFragmentShader); + glLinkProgram(DefaultShaderProgram); + + glGetProgramiv(DefaultShaderProgram, GL_LINK_STATUS, &success); + if(!success) { + glGetProgramInfoLog(DefaultShaderProgram, 512, NULL, infoLog); + printf("Shader linkage fail:\n %s", infoLog); + } + + // Default vertex shader is still needed to link to other effects. + glDeleteShader(DefaultFragmentShader); +} + +static void GL_InitDefaultVerts() { + + unsigned int GLIndices[] = { + 0, 1, 3, + 1, 2, 3 + }; + + // Indices! + glGenVertexArrays(1, &DefaultVerts.VertexArrayObject); + + glGenBuffers(1, &DefaultVerts.ElementBufferObject); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, DefaultVerts.ElementBufferObject); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLIndices), GLIndices, + GL_STATIC_DRAW); + glGenBuffers(1, &DefaultVerts.VertexBufferObject); + + // Our vertices need to be stored in this buffer. + glBindBuffer(GL_ARRAY_BUFFER, DefaultVerts.VertexBufferObject); + glBufferData(GL_ARRAY_BUFFER, sizeof(GL_DefaultVertices), GL_DefaultVertices, GL_STATIC_DRAW); + + // position attribute + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + // texture coordinate (note the last parameter's offset) + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); +} + +void +GL_GenAndBindTexture(GLuint *GLTexture, int Width, int Height, int BytesPerPixel, void *BufferAddress) +{ + glGenTextures(1, GLTexture); + glBindTexture(GL_TEXTURE_2D, *GLTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + int ByteFlag = (BytesPerPixel == 4) ? GL_RGBA : GL_RGBA16; + int ByteFlag2 = (BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT; + glTexImage2D(GL_TEXTURE_2D, 0, ByteFlag, Width, Height, 0, GL_RGBA, ByteFlag2, BufferAddress); +} + +static void +GL_BindDefaultVertexArrays() +{ + glBindVertexArray(DefaultVerts.VertexArrayObject); + // Switch to main buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, DefaultVerts.ElementBufferObject); + glBindBuffer(GL_ARRAY_BUFFER, DefaultVerts.VertexBufferObject); + glBufferData(GL_ARRAY_BUFFER, sizeof(GL_DefaultVertices), GL_DefaultVertices, GL_STATIC_DRAW); + // position attribute + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + // texture coordinate (note the last parameter's offset) + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + +} + +void +GL_InitHWBuffer(gl_effect_layer *Test) +{ + glGenFramebuffers(1, &Test->FramebufferObject); + glGenTextures(1, &Test->Texture); + glGenRenderbuffers(1, &Test->Color_Renderbuffer); + glGenRenderbuffers(1, &Test->Stencil_Renderbuffer); + Test->Initialized = true; +} + +void +GL_DeleteHWBuffer(gl_effect_layer *Test) +{ + glDeleteFramebuffers(1, &Test->FramebufferObject); + glDeleteTextures(1, &Test->Texture); + glDeleteRenderbuffers(1, &Test->Color_Renderbuffer); + glDeleteRenderbuffers(1, &Test->Stencil_Renderbuffer); + Test->Initialized = true; +} + +void +GL_UpdateTexture(gl_effect_layer *Test, void *Data, uint16 Width, uint16 Height, uint16 BytesPerPixel, bool32 Multisample) +{ + glViewport(0, 0, Width, Height); + + if (!Test->Initialized) { + GL_InitHWBuffer(Test); + } + + GLenum Target = GL_TEXTURE_2D; + if (Multisample) + Target = GL_TEXTURE_2D_MULTISAMPLE; + + glBindTexture(Target, Test->Texture); + + int Depth = 0, StencilDepth = 0; + if (BytesPerPixel == 4) { + Depth = GL_RGBA8; + StencilDepth = GL_STENCIL_INDEX8; + } else if (BytesPerPixel == 8) { + Depth = GL_RGBA16; + StencilDepth = GL_STENCIL_INDEX16; + } + + if (Multisample) { + // glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB, Width, Height, GL_TRUE); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); + + glBindRenderbuffer(GL_RENDERBUFFER, Test->Color_Renderbuffer); + + glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, Depth, Width, Height); + + glBindRenderbuffer(GL_RENDERBUFFER, (GLuint)Test->Stencil_Renderbuffer ); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, StencilDepth, Width, Height ); + } else { + glTexParameteri(Target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(Target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(Target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, + // GL_UNSIGNED_BYTE, Data); + glBindTexture(GL_TEXTURE_2D, 0); + + glBindRenderbuffer(GL_RENDERBUFFER, Test->Color_Renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, Depth, Width, Height); + + glBindRenderbuffer(GL_RENDERBUFFER, (GLuint)Test->Stencil_Renderbuffer ); + glRenderbufferStorage(GL_RENDERBUFFER, StencilDepth, Width, Height ); + } + + glBindFramebuffer(GL_FRAMEBUFFER, Test->FramebufferObject); + + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, Test->Color_Renderbuffer); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, Test->Stencil_Renderbuffer); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + printf("incomplete framebuffer"); + Assert(0); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +static uint16 +Effect_GL_InitShader(const char *FragmentShaderEffectSource) +{ + glShaderSource(DefaultVertexShader, 1, &DefaultVertexShaderSource, NULL); + glCompileShader(DefaultVertexShader); + + int success; + char infoLog[512]; + glGetShaderiv(DefaultVertexShader, GL_COMPILE_STATUS, &success); + + if(!success) + { + glGetShaderInfoLog(DefaultVertexShader, 512, NULL, infoLog); + printf("Vertex shader fail:\n %s", infoLog); + } + + uint32 FragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + glShaderSource(FragmentShader, 1, &FragmentShaderEffectSource, NULL); + glCompileShader(FragmentShader); + + glGetShaderiv(FragmentShader, GL_COMPILE_STATUS, &success); + + if(!success) + { + glGetShaderInfoLog(FragmentShader, 512, NULL, infoLog); + printf("Fragment shader fail:\n %s", infoLog); + } + + uint16 ShaderProgram = glCreateProgram(); + + glAttachShader(ShaderProgram, DefaultVertexShader); + glAttachShader(ShaderProgram, FragmentShader); + glLinkProgram(ShaderProgram); + + glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &success); + if(!success) { + glGetProgramInfoLog(ShaderProgram, 512, NULL, infoLog); + printf("Shader linkage fail:\n %s", infoLog); + } + + glDeleteShader(FragmentShader); + + glUseProgram(ShaderProgram); + + return ShaderProgram; +} diff --git a/src/imgui_helper.cpp b/src/imgui_helper.cpp new file mode 100644 index 0000000..dc120e1 --- /dev/null +++ b/src/imgui_helper.cpp @@ -0,0 +1,90 @@ +// Widgets not directly related to drawing UI. + +// Returns a normalized UV position of the composition +static v2 +ImGui_ScreenPointToCompUV(ImVec2 ViewportMin, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 MousePos) +{ + ImVec2 LocalMousePos = MousePos - ViewportMin; + ImVec2 LocalCompPos = CompPos - ViewportMin; + ImVec2 MouseScreenUV = LocalMousePos - LocalCompPos; + ImVec2 Result = MouseScreenUV / CompZoom; + return V2(Result); +} + +// NOTE(fox): We have to do a bit of hackery here to tell how many times the +// mouse has been warped during a drag, since it doesn't seem like we can rely +// on SDL_WarpMouseGlobal to update on the first frame of a WantSetPos request. + +static void +ImGui_WarpMouse(project_state *State, ImVec2 MousePos, ImVec2 Min, ImVec2 Max, int Direction) +{ + if (Direction & 1) { + if (MousePos.x < Min.x) { + State->Warp_WantSetPos = true; + State->Warp_PositionToSet = ImVec2(Max.x - 5, MousePos.y); + State->Warp_PositionInitial = MousePos.x; + State->Warp_Direction = 0; + } + if (MousePos.x > Max.x) { + State->Warp_WantSetPos = true; + State->Warp_PositionToSet = ImVec2(Min.x + 5, MousePos.y); + State->Warp_PositionInitial = MousePos.x; + State->Warp_Direction = 1; + } + } + if (Direction & 2) { + if (MousePos.y < Min.y) { + State->Warp_WantSetPos = true; + State->Warp_PositionToSet = ImVec2(MousePos.x, Max.y - 5); + State->Warp_PositionInitial = MousePos.y; + State->Warp_Direction = 2; + } + if (MousePos.y > Max.y) { + State->Warp_WantSetPos = true; + State->Warp_PositionToSet = ImVec2(MousePos.x, Min.y + 5); + State->Warp_PositionInitial = MousePos.y; + State->Warp_Direction = 3; + } + } +} + +// We record the initial position and the direction of the wrap, and only +// increment the wrap amount when MousePos actually is measured to be what we expect. + +static void +ImGui_WarpMouseFinish(project_state *State, ImVec2 MousePos) +{ + if (State->Warp_Direction == 0) { + if (MousePos.x < State->Warp_PositionInitial) State->Warp_X--; + } else if (State->Warp_Direction == 1) { + if (MousePos.x > State->Warp_PositionInitial) State->Warp_X++; + } else if (State->Warp_Direction == 2) { + if (MousePos.y < State->Warp_PositionInitial) State->Warp_Y--; + } else if (State->Warp_Direction == 3) { + if (MousePos.y > State->Warp_PositionInitial) State->Warp_Y++; + } else { + Assert(0); + } +} + +static bool32 +ImGui_TestBoxSelection_Point(ImVec2 Pos, ImGuiIO &io, bool32 *Test) +{ + bool32 Result = 0; + real32 Y_Top = (io.MouseClickedPos[0].y < io.MousePos.y) ? io.MouseClickedPos[0].y : io.MousePos.y; + real32 Y_Bottom = (io.MouseClickedPos[0].y > io.MousePos.y) ? io.MouseClickedPos[0].y : io.MousePos.y; + real32 X_Left = (io.MouseClickedPos[0].x < io.MousePos.x) ? io.MouseClickedPos[0].x : io.MousePos.x; + real32 X_Right = (io.MouseClickedPos[0].x > io.MousePos.x) ? io.MouseClickedPos[0].x : io.MousePos.x; + + if (Pos.y >= Y_Top && Pos.y <= Y_Bottom && + Pos.x >= X_Left && Pos.x <= X_Right) + { + if (!(*Test)) { + *Test = 1; + Result = 1; + } + } else if (!io.KeyShift) { + *Test = 0; + } + return Result; +} diff --git a/src/imgui_helper_internal.cpp b/src/imgui_helper_internal.cpp new file mode 100644 index 0000000..3267dcc --- /dev/null +++ b/src/imgui_helper_internal.cpp @@ -0,0 +1,222 @@ +#include "imgui_internal_widgets.h" + + +#include "imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +// A modded version of ScalarSlider allowing for the minimum and maximum parts +// of the slider to be draggable by two other buttons. p_mid is from range -1 +// to 1, and s_min and max are from 0-1. +bool ImGui::SliderLevels(const char* label, const char* label2, const char* label3, void* p_mid, void* p_left, void* p_right) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const float SliderMin = 0.1; + const float SliderMax = 10; + const float OtherMin = 0; + const float OtherMax = 1; + const void* p_min = &SliderMin; + const void* p_max = &SliderMax; + const void* o_min = &OtherMin; + const void* o_max = &OtherMax; + ImGuiDataType data_type = ImGuiDataType_Float; + const char *format = "%f"; + + ImGuiSliderFlags flags = ImGuiSliderFlags_NoInput; + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // I'm not well-versed in exactly what ImGui's id system does, but I'm + // pretty sure it's one clickable object equals one ImGui ID. + const ImGuiID id_L = window->GetID(label); + const ImGuiID id_R = window->GetID(label2); + const ImGuiID id_mid = window->GetID(label3); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(total_bb, style.FramePadding.y); + + if (!ItemAdd(total_bb, id_L, &frame_bb, 0)) + return false; + if (!ItemAdd(total_bb, id_R, &frame_bb, 0)) + return false; + if (!ItemAdd(total_bb, id_mid, &frame_bb, 0)) + return false; + + bool any_val_changed; + // Slider behavior + ImRect grab_bb; + const bool value_changed = SliderBehavior(frame_bb, id_L, data_type, p_left, o_min, o_max, format, flags, &grab_bb); + if (value_changed) { + MarkItemEdited(id_L); + any_val_changed = true; + } + + ImRect grab_bb2; + const bool value_changed2 = SliderBehavior(frame_bb, id_R, data_type, p_right, o_min, o_max, format, flags, &grab_bb2); + if (value_changed2) { + MarkItemEdited(id_R); + any_val_changed = true; + } + + const ImRect mid_bb(ImVec2(grab_bb.Max.x, frame_bb.Min.y), ImVec2(grab_bb2.Min.x, frame_bb.Max.y)); + + // Slider behavior + ImRect grab_bb3; + const bool value_changed3 = SliderBehavior(mid_bb, id_mid, data_type, p_mid, p_min, p_max, format, flags | ImGuiSliderFlags_Logarithmic, &grab_bb3); + if (value_changed3) { + MarkItemEdited(id_mid); + any_val_changed = true; + } + + const bool hovered = ItemHoverable(frame_bb, id_L); + + const bool input_requested_by_tabbing = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; + const bool clicked = (hovered && g.IO.MouseClicked[0]); + const bool make_active = (input_requested_by_tabbing || clicked || g.NavActivateId == id_L || g.NavActivateInputId == id_L); + + if (make_active) + { + if (g.IO.MousePos.x < grab_bb.Max.x) { + SetActiveID(id_L, window); + SetFocusID(id_L, window); + } else if (g.IO.MousePos.x > grab_bb2.Min.x) { + SetActiveID(id_R, window); + SetFocusID(id_R, window); + } else { + SetActiveID(id_mid, window); + SetFocusID(id_mid, window); + } + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id_L ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id_L); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + + + // Render grab + if (grab_bb.Max.x > grab_bb.Min.x) + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id_L ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + + // Render grab + if (grab_bb2.Max.x > grab_bb2.Min.x) + window->DrawList->AddRectFilled(grab_bb2.Min, grab_bb2.Max, GetColorU32(g.ActiveId == id_R ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + + // Render grab + if (grab_bb3.Max.x > grab_bb3.Min.x) + window->DrawList->AddRectFilled(grab_bb3.Min, grab_bb3.Max, GetColorU32(g.ActiveId == id_mid ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_mid, format); + if (g.LogEnabled) + LogSetNextTextDecoration("{", "}"); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return any_val_changed; +} + +bool ImGui::TestLine(ImVec2 p0, ImVec2 p1) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + bool Toggle = false; + + ImDrawList* draw_list = window->DrawList; + // ImGuiStyle& style = g.Style; + + ImVec2 point = ImLineClosestPoint(p0, p1, g.IO.MousePos); + + ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); + + if (ImFabs(g.IO.MousePos.x - point.x) < 3 && ImFabs(g.IO.MousePos.y - point.y) < 3 && + point.x != p0.x && point.y != p0.y && + point.x != p1.x && point.y != p1.y) + { + col = ImGui::GetColorU32(ImGuiCol_Button); + Toggle = true; + } + + draw_list->AddLine(p0, p1, col, 2.0f); + + return Toggle; +} + +// Currently not used anywhere outside of debug UI. I'll keep it in case +// there's a place where doing this operation in UI space is useful. Note that +// the version of this function in bezier.cpp differs in precision by about +// 0.001. + +ImVec2 ImGui::RatioToPoint(ImVec2 a, ImVec2 b, float ratio) +{ + ImVec2 ab_dir = b - a; + float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; + float dot = ratio*ab_len_sqr; + return a + ab_dir * dot / ab_len_sqr; +} + +// Returns true when cursor is close to the curve but not too close to the +// beginning/end points. Basically just a wrapper for that ClosestPoint +// function. +bool ImGui::BezierInteractive(ImVec2 p0, ImVec2 p1, ImVec2 p2, ImVec2 p3) +{ + bool hovered = false; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImVec2 point = ImBezierCubicClosestPointCasteljau(p0, p1, p2, p3, g.IO.MousePos, GetStyle().CurveTessellationTol); + + if (ImFabs(g.IO.MousePos.x - point.x) < 3 && ImFabs(g.IO.MousePos.y - point.y) < 3 && + ImFabs(p0.x - point.x) > 3 && ImFabs(p0.y - point.y) > 3 && + ImFabs(p1.x - point.x) > 3 && ImFabs(p1.y - point.y) > 3) + { + hovered = true; + } + + return hovered; +} + +bool ImGui::LineInteractive(ImVec2 p0, ImVec2 p1) +{ + bool hovered = false; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImVec2 point = ImLineClosestPoint(p0, p1, g.IO.MousePos); + + if (ImFabs(g.IO.MousePos.x - point.x) < 3 && ImFabs(g.IO.MousePos.y - point.y) < 3 && + ImFabs(p0.x - point.x) > 3 && ImFabs(p0.y - point.y) > 3 && + ImFabs(p1.x - point.x) > 3 && ImFabs(p1.y - point.y) > 3) + { + hovered = true; + } + + return hovered; +} diff --git a/src/imgui_ui.cpp b/src/imgui_ui.cpp new file mode 100644 index 0000000..612ee1a --- /dev/null +++ b/src/imgui_ui.cpp @@ -0,0 +1,1502 @@ + +#include "imgui_internal_widgets.h" + +#include "imgui_ops.h" + +#include "imgui_helper.cpp" + +#include "imgui_ui_properties.cpp" +#include "imgui_ui_timeline.cpp" + +#if DEBUG +#include "imgui_ui_debug.cpp" +#endif +#if STABLE +#include "imgui_ui_stable_diffusion.cpp" +#endif + +static void +ImGui_File(project_data *File, project_state *State, memory *Memory, ImGuiIO io, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) +{ + ImGui::Begin("Files"); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + + if (ImGui::IsKeyPressed(ImGuiKey_Backspace)) { + // TODO(fox): Delete sources code! + + /* + uint64 SortSize = (sizeof(uint16) * File->Comp_Count); + void *SortedArray = Memory_PushScratch(Memory, SortSize); + uint16 *SelectedSourceIndex = (uint16 *)SortedArray; + int SelectedSourceCount = 0; + + int h = 0, c = 0, i = 0; + int SourceCount = File->Source_Count; + while (Block_Loop(Memory, F_Sources, SourceCount, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + if (Source->IsSelected) { + SelectedSourceIndex[SelectedSourceCount] = i; + SelectedSourceCount++; + } + } + + h = 0, c = 0, i = 0; + int LayerCount = File->Layer_Count; + while (Block_Loop(Memory, F_Layers, LayerCount, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + for (int b = 0; b < SelectedSourceCount; b++) { + if (SelectedSourceIndex[b] == Layer->Block_Source_Index) { + } + } + } + + Memory_PopScratch(Memory, SortSize); + */ + + /* + bool32 CommitAction = 0; + while (Block_Loop(Memory, F_Sources, SourceCount, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + if (Source->IsSelected) { + if (!CommitAction) { + History_Entry_Commit(Memory, "Delete source"); + CommitAction = 1; + } + Source_Delete(File, Memory, i); + } + } + if (CommitAction) + History_Entry_End(Memory); + */ + } + + for (int c = 0; c < File->Comp_Count; c++) { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, c); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Comp->Name_String_Index); + ImGui::Text(String->Char); + } + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Sources, File->Source_Count, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); + ImGui::Selectable(String->Char, Source->IsSelected); + if (ImGui::IsItemClicked() || ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (!io.KeyShift && !Source->IsSelected) { + Source_DeselectAll(File, Memory); + } + Source->IsSelected = 1; + } + ImGui::OpenPopupOnItemClick("sourcecontext", ImGuiPopupFlags_MouseButtonRight); + } + + if (ImGui::BeginPopup("sourcecontext")) { + if (ImGui::MenuItem("Create layer from source")) { + State->HotkeyInput = hotkey_newlayerfromsource; + } + ImGui::EndPopup(); + } + + // if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) + // Source_DeselectAll(File, Memory); + +#if DEBUG + + for (int i = 0; i < Debug.Temp.WatchedProperties; i++) { + if (Debug.Temp.DebugPropertyType[i] == d_float) { + ImGui::Text("%s: %f", Debug.Temp.String[i], Debug.Temp.Val[i].f); + } else if (Debug.Temp.DebugPropertyType[i] == d_int) { + ImGui::Text("%s: %i", Debug.Temp.String[i], Debug.Temp.Val[i].i); + } else if (Debug.Temp.DebugPropertyType[i] == d_uint) { + ImGui::Text("%s: %u", Debug.Temp.String[i], Debug.Temp.Val[i].u); + } + } +#endif + ImGui::End(); +} + +static void +ImGui_ColorPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) +{ + ImGuiStyle& style = ImGui::GetStyle(); + + ImGui::Begin("Colors"); + + ImGuiColorEditFlags flags_primary = ImGuiColorEditFlags_AlphaPreview | + ImGuiColorEditFlags_Float; + ImGuiColorEditFlags flags_picker = ImGuiColorEditFlags_PickerHueBar | + ImGuiColorEditFlags_AlphaBar | + ImGuiColorEditFlags_NoSmallPreview | + ImGuiColorEditFlags_NoSidePreview | + ImGuiColorEditFlags_DisplayRGB | + ImGuiColorEditFlags_DisplayHSV | + ImGuiColorEditFlags_DisplayHex; + + // Dim window if it's not active so there's not a big saturation square in + // the corner of my vision while I'm editing. Personal preference. + real32 AlphaMult = (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) ? 1.0f : 0.3f; + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * AlphaMult); + + ImGui::ColorPicker4("##maincolorpicker", &UI->Color.r, flags_primary | flags_picker); + + ImGui::PopStyleVar(); + + if (ImGui::ColorButton("##primarycolor", *(ImVec4*)&UI->Color.r, flags_primary, ImVec2(20, 20))) + { + v4 Temp = UI->Color; + UI->Color = UI->AltColor; + UI->AltColor = Temp; + } + if (ImGui::ColorButton("##secondarycolor", *(ImVec4*)&UI->AltColor.r, flags_primary, ImVec2(20, 20))) + { + v4 Temp = UI->Color; + UI->Color = UI->AltColor; + UI->AltColor = Temp; + } + + if (State->Tool == tool_brush) { + real32 BrushSizeMin = 0; + real32 BrushSizeMax = 1024; + real32 BrushHardnessMin = 0.5; + real32 BrushHardnessMax = 100; + real32 BrushSpacingMin = 0.1; + real32 BrushSpacingMax = 100; + if (ImGui::DragScalar("Size", ImGuiDataType_Float, &State->Brush.Size, 1, &BrushSizeMin, &BrushSizeMax, "%.3f")) { + Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); + State_BindBrushTexture(Memory, &State->Brush, 4); + } + if (ImGui::DragScalar("Hardness", ImGuiDataType_Float, &State->Brush.Hardness, 1, &BrushHardnessMin, &BrushHardnessMax, "%.3f", ImGuiSliderFlags_Logarithmic)) { + Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); + State_BindBrushTexture(Memory, &State->Brush, 4); + } + if (ImGui::DragScalar("Spacing", ImGuiDataType_Float, &State->Brush.Spacing, 1, &BrushSpacingMin, &BrushSpacingMax, "%.3f", ImGuiSliderFlags_Logarithmic)) { + Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); + State_BindBrushTexture(Memory, &State->Brush, 4); + } + } + + ImGui::End(); +} + +static void +ImGui_Viewport_Toolbar(project_state *State, ImDrawList *draw_list) +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + // ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(50, 50, 50, 0)); + + real32 IconSize = ImGui::GetFontSize() * 4; + int ToolCount = (int)tool_count; + ImVec2 ButtonSize(IconSize, IconSize); + ImVec2 WindowSize(IconSize, IconSize * ToolCount); + ImGui::BeginChild("Toolbar", WindowSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + for (int i = 0; i < ToolCount; i++) { + ImGui::PushID(i); + // draw_list->AddImage((void *)(intptr_t)State->ToolIconTex[i], Min, Max); + if ((int)State->Tool == i) { + ImVec2 Min = ImGui::GetCursorScreenPos(); + ImVec2 Max = Min + ButtonSize; + draw_list->AddRectFilled(Min, Max, IM_COL32(255, 255, 255, 128)); + } + ImGui::Button(ToolName[i], ButtonSize); + if (ImGui::IsItemActivated()) { + State->Tool = (tool)i; + } + ImGui::PopID(); + } + ImGui::EndChild(); + + // ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); +} + +static void +ImGui_Viewport_BrushUI(project_state *State, memory *Memory, ImVec2 ViewportMin, ImVec2 ViewportMax, ImVec2 CompZoom, ImGuiIO io, uint16 Width, uint16 Height) +{ + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + if (State->Tool == tool_brush) { + + if (ImGui::IsKeyPressed(ImGuiKey_ModAlt, false)) { + State->Brush.UIPos = io.MousePos; + } + + ImVec2 CompScale = CompZoom / ImVec2(Width, Height); + ImVec2 BrushSize = CompScale * State->Brush.Size; + ImVec2 MinBounds = State->Brush.UIPos - BrushSize/2; + ImVec2 MaxBounds = MinBounds + BrushSize; + + // if (io.KeyCtrl) { + // ImGui::SetCursorScreenPos(State->Brush.UIPos); + // char buf[256]; + // sprintf(buf, "RGBA: %.1f, %.1f, %.1f, %.1f", State->Brush.Size, State->Brush.Hardness); + // } + + if (io.KeyAlt) { + draw_list->PushClipRect(ViewportMin, ViewportMax, true); + draw_list->AddImage((void *)(intptr_t)State->Brush.GLTexture, MinBounds, MaxBounds, ImVec2(0, 0), ImVec2(1, 1), 1); + draw_list->PopClipRect(); + ImGui::SetCursorScreenPos(State->Brush.UIPos); + char buf[256]; + sprintf(buf, "Size: %.1f, Hardness: %.1f", State->Brush.Size, State->Brush.Hardness); + ImGui::Text(buf); + if (io.MouseDelta.x || io.MouseDelta.y) { + ImVec2 Delta = io.MouseDelta; + State->Brush.Size += Delta.x; + State->Brush.Hardness += Delta.y*State->Brush.Hardness/100; + if (State->Brush.Size < 0) + State->Brush.Size = 0; + if (State->Brush.Size > 1024) + State->Brush.Size = 1024; + if (State->Brush.Hardness < 0.5) + State->Brush.Hardness = 0.5; + if (State->Brush.Hardness > 100) + State->Brush.Hardness = 100; + Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); + State_BindBrushTexture(Memory, &State->Brush, 4); + } + } + } +} + +static void +ImGui_Viewport_TransformUI(project_data *File, project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, ImGuiIO &io, + interact_transform *Interact, ImVec2 ViewportMin, uint32 CompWidth, uint32 CompHeight, uint16 *SortedKeyframeArray) +{ + v2 InteractMin = Interact->Min + Interact->Position; + v2 InteractMax = Interact->Max + Interact->Position; + + v2 BoxLength = InteractMax - InteractMin; + v2 Center = InteractMax - (BoxLength/2); + + real32 Point0X = Center.x - InteractMin.x; + real32 Point0Y = Center.y - InteractMin.y; + + real32 Rad = Interact->Radians; + + v2 XAxis = (Point0X * Interact->Scale)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (Point0Y * -Interact->Scale)*V2(sin(Rad), -cos(Rad)); + + // Points are clockwise starting from the top left. + real32 X0 = -XAxis.x - YAxis.x + Center.x; + real32 Y0 = -XAxis.y - YAxis.y + Center.y; + real32 X1 = X0 + XAxis.x*2; + real32 Y1 = Y0 + XAxis.y*2; + real32 X2 = X1 + YAxis.x*2; + real32 Y2 = Y1 + YAxis.y*2; + real32 X3 = X2 - XAxis.x*2; + real32 Y3 = Y2 - XAxis.y*2; + + // Midway points. + real32 Mid_X0 = X0 + XAxis.x; + real32 Mid_Y0 = Y0 + XAxis.y; + real32 Mid_X1 = X1 + YAxis.x; + real32 Mid_Y1 = Y1 + YAxis.y; + real32 Mid_X2 = X2 - XAxis.x; + real32 Mid_Y2 = Y2 - XAxis.y; + real32 Mid_X3 = X3 - YAxis.x; + real32 Mid_Y3 = Y3 - YAxis.y; + + ImVec2 CompScale = UI->CompZoom / ImVec2(CompWidth, CompHeight); + + ImVec2 P[4]; + P[0] = ImVec2(X0, Y0)*CompScale + UI->CompPos; + P[1] = ImVec2(X1, Y1)*CompScale + UI->CompPos; + P[2] = ImVec2(X2, Y2)*CompScale + UI->CompPos; + P[3] = ImVec2(X3, Y3)*CompScale + UI->CompPos; + + ImVec2 Mid_P[4]; + Mid_P[0] = ImVec2(Mid_X0, Mid_Y0)*CompScale + UI->CompPos; + Mid_P[1] = ImVec2(Mid_X1, Mid_Y1)*CompScale + UI->CompPos; + Mid_P[2] = ImVec2(Mid_X2, Mid_Y2)*CompScale + UI->CompPos; + Mid_P[3] = ImVec2(Mid_X3, Mid_Y3)*CompScale + UI->CompPos; + + ImU32 wcol = ImGui::GetColorU32(ImGuiCol_Text); + draw_list->AddLine(P[0], P[1], wcol, 2.0f); + draw_list->AddLine(P[1], P[2], wcol, 2.0f); + draw_list->AddLine(P[2], P[3], wcol, 2.0f); + draw_list->AddLine(P[3], P[0], wcol, 2.0f); + + v2 XAxis2 = (BoxLength*CompScale.x)*V2(cos(Rad), sin(Rad)); + v2 YAxis2 = (BoxLength*CompScale.y)*V2(sin(Rad), -cos(Rad)); + + v2 XAxisPerp = (1.0f / LengthSq(XAxis))*XAxis; + v2 YAxisPerp = (1.0f / LengthSq(YAxis))*YAxis; + + // real32 LocalX = ((io.MousePos.x - UI->CompPos.x) - Center.x) ; + // real32 LocalY = ((io.MousePos.y - UI->CompPos.y) - Center.y) ; + layer_transforms BoxTransforms = { Center.x, Center.y, 0.5, 0.5, (real32)(Interact->Radians / (PI / 180)), Interact->Scale }; + v2 LayerPoint = Transform_ScreenSpaceToLocal(BoxTransforms, CompWidth, CompHeight, BoxLength.x, BoxLength.y, UI->CompPos, UI->CompZoom, ViewportMin, io.MousePos); + + real32 U = LayerPoint.x / BoxLength.x; + real32 V = LayerPoint.y / BoxLength.y; + + ImVec2 ScaleHandleSize(50, 50); + + bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z); + + // First do the halfway scale points, since they don't need UVs considered: + for (int i = 0; i < 4; i++) { + ImGui::SetCursorScreenPos(Mid_P[i] - ScaleHandleSize/2); + ImGui::PushID(i); + + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertFloat4ToU32(ImVec4(0.6f, 0.0f, 0.3f, 1.0f))); + ImGui::Button("##ScaleMids", ScaleHandleSize); + ImGui::PopStyleColor(); + + if (ImGui::IsItemActivated() && !OtherActions) { + State->InteractTransformMode = 1; + } + + if (State->InteractTransformMode == 1 && ImGui::IsItemActive()) + { + uint32 side = i; + if (side == 0) { + Interact->Scale -= io.MouseDelta.y / BoxLength.y; + Interact->Position.y += io.MouseDelta.y / 2; + } else if (side == 1) { + Interact->Scale += io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + } else if (side == 2) { + Interact->Scale += io.MouseDelta.y / BoxLength.y; + Interact->Position.y += io.MouseDelta.y / 2; + } else if (side == 3) { + Interact->Scale -= io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + } + } + ImGui::PopID(); + } + + bool32 InBounds = false; + // Scale if cursor is on button within the UV, rotate if outside UV, and position if a non-button is dragged. + if (U >= 0.0f && U <= 1.0f && V >= 0.0f && V <= 1.0f) + { + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertFloat4ToU32(ImVec4(0.6f, 0.0f, 0.3f, 1.0f))); + InBounds = true; + } + + for (int i = 0; i < 4; i++) { + ImGui::SetCursorScreenPos(P[i] - ScaleHandleSize/2); + ImGui::PushID(i); + ImGui::Button("##ScaleRotateCorners", ScaleHandleSize); + + if (ImGui::IsItemActivated() && !OtherActions) { + if (InBounds) + State->InteractTransformMode = 1; + else + State->InteractTransformMode = 2; + } + + // Scale part + if (State->InteractTransformMode == 1 && ImGui::IsItemActive()) + { + // TODO(fox): Corner dragging scale only works in the X + // axis. Mostly feels right when dragged how you expect, + // but I'll fix it if someone complains. + uint32 side = i; + if (side == 0) { + Interact->Scale -= io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + Interact->Position.y += io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; + } else if (side == 1) { + Interact->Scale += io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + Interact->Position.y -= io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; + } else if (side == 2) { + Interact->Scale += io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + Interact->Position.y += io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; + } else if (side == 3) { + Interact->Scale -= io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + Interact->Position.y -= io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; + } + } + + // Rotation part + if (State->InteractTransformMode == 2 && ImGui::IsItemActive()) + { + real32 LocalX = (io.MousePos.x - UI->CompPos.x)/CompScale.x - InteractMin.x - (BoxLength.x/2); + real32 LocalY = (io.MousePos.y - UI->CompPos.y)/CompScale.y - InteractMin.y - (BoxLength.y/2); + + real32 Slope_Mouse = LocalY/LocalX; + real32 Slope_Corner = 0; + real32 Slope_Flipped = 0; + real32 Dot = 0; + + // TODO(fox) learn basic geometry to do this properly + + // We find the angle between the direction of whichever corner the + // mouse is grabbing (Slope_Corner) and the mouse's current + // position (Slope_Mouse) to get ExtraRadians. The calculation only + // works between -90 and 90, so I take the dot product of the + // opposite edge of the corner and add the extra degrees when it's negative. + + v2 SlopeDot = V2(BoxLength.x, BoxLength.y); + // top left clockwise + uint32 side = i; + if (side == 0) { + Slope_Corner = BoxLength.y / BoxLength.x; + Slope_Flipped = -BoxLength.x / BoxLength.y; + Dot = LocalX * -SlopeDot.x + LocalY * -SlopeDot.y; + } else if (side == 1) { + Slope_Corner = -BoxLength.y / BoxLength.x; + Slope_Flipped = BoxLength.x / BoxLength.y; + Dot = LocalX * SlopeDot.x + LocalY * -SlopeDot.y; + } else if (side == 2) { + Slope_Corner = BoxLength.y / BoxLength.x; + Slope_Flipped = -BoxLength.x / BoxLength.y; + Dot = LocalX * SlopeDot.x + LocalY * SlopeDot.y; + } else if (side == 3) { + Slope_Corner = -BoxLength.y / BoxLength.x; + Slope_Flipped = BoxLength.x / BoxLength.y; + Dot = LocalX * -SlopeDot.x + LocalY * SlopeDot.y; + } + + Interact->Radians = atan((Slope_Mouse - Slope_Corner) / (1 + Slope_Mouse * Slope_Corner)); + real32 ExtraRadians2 = atan((Slope_Mouse - Slope_Flipped) / (1 + Slope_Mouse * Slope_Flipped)); + + if (Dot < 0) { + if (Interact->Radians < 0) { + Interact->Radians = (90 * (PI / 180)) + ExtraRadians2; + } else { + Interact->Radians = (-90 * (PI / 180)) + ExtraRadians2; + } + } + } + + ImGui::PopID(); + } + + if (!State->InteractTransformMode && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && InBounds && !OtherActions) + State->InteractTransformMode = 3; + + if (State->InteractTransformMode == 3) { + Interact->Position.x += (real32)io.MouseDelta.x/CompScale.x; + Interact->Position.y += (real32)io.MouseDelta.y/CompScale.y; + } + + if (State->InteractTransformMode) + { + if (io.MouseDelta.x || io.MouseDelta.y) + State->UpdateFrame = true; + if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) + State->InteractTransformMode = 0; + } + + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + State->UpdateFrame = true; + } + + // Second condition so you don't have to reach for Enter. + if (ImGui::IsKeyPressed(ImGuiKey_Enter) || (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && io.KeyCtrl)) { + int h = 0, c = 0, i = 0; + if (!io.KeyCtrl) + History_Entry_Commit(Memory, "Transform layers"); + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected == 1) { + if (io.KeyCtrl) { + layer_transforms T = Layer_GetTransforms(Layer); + Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); + property_channel *Property[4] = { &Layer->x, &Layer->y, &Layer->rotation, &Layer->scale }; + real32 Val[4] = { T.x, T.y, T.rotation, T.scale }; + for (int a = 0; a < 4; a++) { + if (Property[a]->CurrentValue != Val[a]) { + History_Entry_Commit(Memory, "Add keyframe"); + bezier_point Point = { 1, {(real32)State->Frame_Current, Val[a], -1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 }; + uint16 *ArrayLocation = Property_GetSortedArray(SortedKeyframeArray, State->MostRecentlySelectedLayer, h); + Bezier_Add(Memory, F_Layers, Property[a], Point, ArrayLocation); + History_Entry_End(Memory); + } + } + } else { + History_Action_Swap(Memory, F_File, sizeof(Layer->x.CurrentValue), &Layer->x.CurrentValue); + History_Action_Swap(Memory, F_File, sizeof(Layer->y.CurrentValue), &Layer->y.CurrentValue); + History_Action_Swap(Memory, F_File, sizeof(Layer->scale.CurrentValue), &Layer->scale.CurrentValue); + History_Action_Swap(Memory, F_File, sizeof(Layer->rotation.CurrentValue), &Layer->rotation.CurrentValue); + Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &Layer->x.CurrentValue, &Layer->y.CurrentValue, &Layer->rotation.CurrentValue, &Layer->scale.CurrentValue); + } + } + } + if (!io.KeyCtrl) + History_Entry_End(Memory); + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + State->UpdateFrame = true; + } + + if (InBounds == true) { + ImGui::PopStyleColor(); + } + +} + +static void +ImGui_Viewport_SelectedLayerUI(project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, block_composition *MainComp, uint32 CompIndex, block_layer *ParentLayer[4], uint32 Recursions, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) +{ + sorted_comp_array *SortedCompStart = &SortedCompArray[CompIndex]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); + ImU32 wcol = ImGui::GetColorU32(ImGuiCol_Text); + for (int i = 0; i < SortedCompStart->LayerCount; i++) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsPrecomp) { + ParentLayer[Recursions] = Layer; + ImGui_Viewport_SelectedLayerUI(State, Memory, UI, draw_list, MainComp, Layer->Block_Source_Index, ParentLayer, Recursions + 1, SortedCompArray, SortedLayerArray); + } + if (Layer->IsSelected) { + uint32 Width = 0, Height = 0; + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + Width = Source->Width; + Height = Source->Height; + } else { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + Width = Comp->Width; + Height = Comp->Height; + } + + v2 Point[5] = { V2(Width*Layer->ax.CurrentValue, Height*Layer->ay.CurrentValue), V2(0, 0), V2(Width, 0), V2(0, Height), V2(Width, Height) }; + + layer_transforms T = Layer_GetTransforms(Layer); + + if (State->Interact_Active == interact_type_viewport_transform && Layer->IsSelected == 1) { + Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); + } + + v2 NewPos[5]; + for (int i = 0; i < 5; i++) { + NewPos[i] = TransformPoint(T, Width, Height, Point[i]); + } + + int i = 0; + while (i < Recursions) { + T = Layer_GetTransforms(ParentLayer[i]); + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, ParentLayer[i]->Block_Source_Index); + Width = Comp->Width; + Height = Comp->Height; + for (int i = 0; i < 5; i++) { + NewPos[i] = TransformPoint(T, Width, Height, NewPos[i]); + } + i++; + } + + ImVec2 ScreenPoint[5]; + for (int i = 0; i < 5; i++) { + v2 CompUV = NewPos[i] / V2(MainComp->Width, MainComp->Height); + + ScreenPoint[i] = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, + UI->CompPos.y + CompUV.y * UI->CompZoom.y); + + } + if (State->Tool != tool_brush) { + ImU32 wcol2 = IM_COL32(10, 10, 10, 255); + draw_list->AddNgon(ScreenPoint[0], 10, wcol2, 8, 9.0f); + draw_list->AddNgon(ScreenPoint[0], 10, wcol, 8, 5.0f); + } + draw_list->AddLine(ScreenPoint[1], ScreenPoint[2], wcol, 2.0f); + draw_list->AddLine(ScreenPoint[2], ScreenPoint[4], wcol, 2.0f); + draw_list->AddLine(ScreenPoint[1], ScreenPoint[3], wcol, 2.0f); + draw_list->AddLine(ScreenPoint[3], ScreenPoint[4], wcol, 2.0f); + } + } +} + +static void +ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, GLuint textureID, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 *SortedKeyframeArray) +{ + bool open = true; + ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) { + State->FocusedWindow = focus_viewport; + } + + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + + ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); + ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); + ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); + + if (State->Initializing) { + UI->CompZoom = ImVec2(MainComp->Width, MainComp->Height); + UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), + ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); + } + + ImVec2 CompPosMin = ImVec2(UI->CompPos.x, UI->CompPos.y); + ImVec2 CompPosMax = ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); + draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); + draw_list->AddRect(CompPosMin, CompPosMax, IM_COL32(255, 255, 255, 55)); + + real32 FontSize = ImGui::GetFontSize(); + ImGui::SetCursorScreenPos(ImVec2(ViewportMax.x - FontSize*2, ViewportMin.y + ViewportScale.y - FontSize*3.0)); + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 80)); + if (ImGui::Button("?")) + State->ImGuiPopups = popup_keybinds; + ImGui::PopStyleColor(); + + + // Actual composition texture + draw_list->PushClipRect(ViewportMin, ViewportMax, true); + draw_list->AddImage((void *)(intptr_t)textureID, CompPosMin, CompPosMax); + draw_list->PopClipRect(); + + // UI+interaction for layer + if (State->MostRecentlySelectedLayer > -1) + { + block_layer *ParentLayer[4]; + ImGui_Viewport_SelectedLayerUI(State, Memory, UI, draw_list, MainComp, File->PrincipalCompIndex, ParentLayer, 0, SortedCompArray, SortedLayerArray); + if (State->Interact_Active == interact_type_viewport_transform) { + ImGui_Viewport_TransformUI(File, State, Memory, UI, draw_list, io, (interact_transform *)&State->Interact_Offset[0], ViewportMin, MainComp->Width, MainComp->Height, SortedKeyframeArray); + } + } + + + + // Interactions for dragging and zooming + ImGui::SetCursorScreenPos(ViewportMin); + + ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + bool32 IsHovered = ImGui::IsItemHovered(); +#if 1 + bool32 IsActive = ImGui::IsItemActive(); + bool32 IsActivated = ImGui::IsItemActivated(); + bool32 IsDeactivated = ImGui::IsItemDeactivated(); +#else + bool32 IsActive = ImGui::IsKeyDown(ImGuiKey_3); + bool32 IsActivated = ImGui::IsKeyPressed(ImGuiKey_3); + bool32 IsDeactivated = ImGui::IsKeyReleased(ImGuiKey_3); +#endif + + if (IsHovered && IsActivated && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) + { + // Point to zoom in on if Z is held + State->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); + + if (State->Tool == tool_brush && State->Interact_Active != interact_type_brush && !ImGui::IsKeyDown(ImGuiKey_Z)) { + if (!io.KeyCtrl) { + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) + { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + if (Layer->IsSelected && Source->Type == source_type_principal) { + Assert(Source->BytesPerPixel == 4); + Arbitrary_Zero((uint8 *)State->Brush.TransientBitmap, 2048*2048*4); + State->Interact_Active = interact_type_brush; + State->Brush.LayerToPaint_Index = i; + break; + } + } + } + } + if (State->Brush.LayerToPaint_Index == -1) { + State->HotkeyInput = hotkey_newpaintlayer; + } + } + + // Layer selection + if (!ImGui::IsKeyDown(ImGuiKey_Z) && State->Tool == tool_default && State->Interact_Active == interact_type_none) { + int32 Selection = Layer_TestSelection(Memory, State, UI, SortedCompArray, SortedLayerArray, File->PrincipalCompIndex); + if (!io.KeyShift && State->Interact_Active == interact_type_none) + Layer_DeselectAll(File, State, Memory); + if (Selection != -1) + Layer_Select(Memory, State, Selection); + } + } + + /* + if (State->Interact_Active == interact_type_viewport_transform) { + interact_transform *Interact = (interact_transform *)&State->Interact_Offset[0]; + ImVec2 DragDelta = io.MousePos - Interact->OGPos; + Interact->Position = V2(DragDelta.x, DragDelta.y); + if (io.MouseDelta.x || io.MouseDelta.y) + State->UpdateFrame = true; + } + */ + + if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) + { + UI->CompPos.x += io.MouseDelta.x; + UI->CompPos.y += io.MouseDelta.y; + } + + + bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z) || ImGui::IsMouseDown(ImGuiMouseButton_Right); + if (State->Tool == tool_brush && State->Interact_Active == interact_type_brush) { + Assert(State->Brush.LayerToPaint_Index != -1); + if (IsActive && !OtherActions) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->Brush.LayerToPaint_Index); + layer_transforms T_Layer = Layer_GetTransforms(Layer); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + ImVec2 MouseDelta = io.MouseDelta; + real32 Delta = MouseDelta.x + MouseDelta.y; + v2 PrincipalCompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); + v2 LayerPos = Layer_TraverseForPoint(File, State, Memory, PrincipalCompUV, SortedCompArray, SortedLayerArray); + if (IsActivated) { + RenderQueue_AddBrush(State, LayerPos); + } else if (Delta != 0.0f) { + v2 PrevPos = State->Brush.PrevPos; + v2 Delta = PrevPos - LayerPos; + real32 Dist = sqrt(LengthSq(Delta)); + if (Dist > State->Brush.Spacing) { + RenderQueue_AddBrush(State, LayerPos); + } + } + State->UpdateFrame = true; + } + + if (IsDeactivated) { + RenderQueue_AddBlit(State); + } + } + + if (ImGui::IsKeyDown(ImGuiKey_Z) && ImGui::IsWindowHovered()) { + if (IsActive) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + else + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + + real32 Distance = 0; + if (IsActive) { + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f)) + Distance = io.MouseDelta.x + io.MouseDelta.y; + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + Distance = 200; + } + if (Distance && ImGui::IsKeyDown(ImGuiKey_Z)) + { + if (io.KeyShift) + Distance *= -1; + UI->CompZoom.x += (Distance)*(real32)MainComp->Width/MainComp->Height; + UI->CompZoom.y += (Distance); + UI->CompPos.x -= ((Distance)*(real32)MainComp->Width/MainComp->Height)*State->TempZoomRatio.x; + UI->CompPos.y -= Distance*State->TempZoomRatio.y; + } + + ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMin.y + ViewportScale.y - FontSize*1.5)); + + ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / MainComp->Width)); + if (State->MsgTime > 0) { + ImGui::SameLine(); + ImGui::SetCursorPosX((ViewportScale.x / 5)*4); + ImGui::Text(State->Msg); + State->MsgTime--; + } + + ImGui::SetCursorScreenPos(ViewportMin); + ImGui_Viewport_Toolbar(State, draw_list); + ImGui_Viewport_BrushUI(State, Memory, ViewportMin, ViewportMax, UI->CompPos, io, MainComp->Width, MainComp->Height); + + ImGui::End(); +} + +#include "keybinds.h" + +static void +ImGui_Key_GetUIInfo(key_entry KeyEntry, real32 KeySize, ImVec2 *Offset_ScreenPos, ImVec2 *KeyScreenSize) { + ImVec2 Extra(0, 0); + if (KeyEntry.Sector == 0) { + if (KeyEntry.Offset.x != 0) { + if (KeyEntry.Offset.y == 1) { + Extra.x += 0.5; + } + if (KeyEntry.Offset.y == 2) { + Extra.x += 0.75; + } + if (KeyEntry.Offset.y == 3) { + Extra.x += 1.5; + } + } + } + *Offset_ScreenPos = ImVec2(KeySize, KeySize) * (SectorOffset[KeyEntry.Sector] + Extra + KeyEntry.Offset); + *KeyScreenSize = (KeyEntry.WidthRatio > 0.0f) ? ImVec2(KeySize * KeyEntry.WidthRatio, KeySize) : ImVec2(KeySize, KeySize * -KeyEntry.WidthRatio); +} + +static void +ImGui_KeybindUI(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) +{ + real32 KeySize = ImGui::GetFontSize()*2; + real32 KeyboardWidth = KeySize * 23.5; + real32 KeyboardHeight = KeySize * 7; + ImVec2 WindowSize = ImGui::GetWindowSize(); + ImVec2 WindowMinAbs = ImGui::GetWindowPos(); + ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; + ImVec2 KeyboardPos((WindowSize.x - KeyboardWidth) / 2, KeySize*2); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 SectorOffset[4] = { ImVec2(0, 1.25), ImVec2(0,0), ImVec2(15.25, 1.25), ImVec2(19.5, 1.25) }; + + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(190, 0, 50, 180)); + if (ImGui::Button("X")) + ImGui::CloseCurrentPopup(); + ImGui::PopStyleColor(); + + State->Split_KeybindUI.Split(draw_list, 2); + State->Split_KeybindUI.SetCurrentChannel(draw_list, 1); + + // keyboard + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 180)); + for (int k = 0; k < AmountOf(KeyEntries); k++) { + key_entry KeyEntry = KeyEntries[k]; + ImVec2 Offset_ScreenPos(0,0); + ImVec2 KeyScreenSize(0,0); + ImGui_Key_GetUIInfo(KeyEntry, KeySize, &Offset_ScreenPos, &KeyScreenSize); + if (KeyEntry.Name[0] != '\0') { + ImGui::PushID(k); + ImGui::SetCursorScreenPos(WindowMinAbs + KeyboardPos + Offset_ScreenPos); + ImGui::Button(KeyEntry.Name, KeyScreenSize); + ImGui::PopID(); + } + } + ImGui::PopStyleColor(); + + State->Split_KeybindUI.SetCurrentChannel(draw_list, 0); + + // list + ImVec2 SubwindowMinPos(WindowMinAbs + KeyboardPos + ImVec2(0, KeyboardHeight + KeySize)); + ImVec2 SubwindowSize(KeyboardWidth, WindowSize.y - KeyboardHeight - KeySize*4); + ImGui::SetCursorScreenPos(SubwindowMinPos); + ImGui::BeginChild("Keybinds info", SubwindowSize); + key_mode CurrentKeyMode = (key_mode)9999; + for (int a = 0; a < AmountOf(ShortcutArray); a++) { + shortcut_entry ShortcutEntry = ShortcutArray[a]; + + // header info + if (CurrentKeyMode != ShortcutEntry.Mode) { + ImGui::Dummy(ImVec2(1, KeySize)); + CurrentKeyMode = ShortcutEntry.Mode; + ImVec2 Size = ImGui::CalcTextSize(KeyModeTitles[CurrentKeyMode]); + ImGui::SetCursorPosX(((SubwindowSize.x / 2) - (Size.x / 2))); + ImGui::Text(KeyModeTitles[CurrentKeyMode]); + ImGui::SetCursorPosX((SubwindowSize.x / 2) - (ImGui::CalcTextSize("-----").x / 2)); + ImGui::TextColored(ImColor(UI->LayerColors[CurrentKeyMode]), "-----"); + ImGui::Dummy(ImVec2(1, KeySize/4)); + while (ShortcutEntry.Key == ImGuiKey_None) { + ImVec2 Size = ImGui::CalcTextSize(ShortcutEntry.Name); + ImGui::SetCursorPosX(((SubwindowSize.x / 2) - (Size.x / 2))); + ImGui::Text(ShortcutEntry.Name); + a++; + ShortcutEntry = ShortcutArray[a]; + } + ImGui::Dummy(ImVec2(1, KeySize/2)); + } + + // shortcut text + key + Assert(ShortcutEntry.Key != ImGuiKey_None); + key_entry KeyEntry = KeyEntries[ShortcutEntry.Key - ImGuiKey_Tab]; + real32 Padding = KeySize; + ImGui::Dummy(ImVec2(Padding, 1)); + ImGui::SameLine(); + ImGui::Text(ShortcutEntry.Name); + ImGui::SameLine(); + char buf[64]; + if (ShortcutEntry.Mods == Mod_None) { + sprintf(buf, "%s", KeyEntry.Name); + } else if (ShortcutEntry.Mods == Mod_Shift) { + sprintf(buf, "%s", KeyEntry.ShiftName); + } else { + sprintf(buf, "%s + %s", KeyModText[ShortcutEntry.Mods], KeyEntry.Name); + } + ImVec2 Size = ImGui::CalcTextSize(buf); + ImGui::SetCursorPosX(SubwindowSize.x - Size.x - Padding*1.5); + ImGui::Text(buf); + + // indicator on keyboard + ImVec2 Offset_ScreenPos(0,0); + ImVec2 KeyScreenSize(0,0); + ImGui_Key_GetUIInfo(KeyEntry, KeySize, &Offset_ScreenPos, &KeyScreenSize); + real32 ModeSliverSize = KeySize / key_mode_count; + Offset_ScreenPos.x += (ShortcutEntry.Mode * ModeSliverSize); + KeyScreenSize = ImVec2(ModeSliverSize, KeyScreenSize.y); + ImVec2 MinPos = WindowMinAbs + KeyboardPos + Offset_ScreenPos; + draw_list->AddRectFilled(MinPos, MinPos + KeyScreenSize, UI->LayerColors[CurrentKeyMode]); + } + ImGui::EndChild(); + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + + State->Split_KeybindUI.Merge(draw_list); +} + +static void +ImGui_Popups(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) +{ + switch (State->ImGuiPopups) + { + case popup_none: + { + } break; + case popup_saveas: + { + ImGui::OpenPopup("Save as"); + ImGui::SetKeyboardFocusHere(); + } break; + case popup_keybinds: + { + ImGui::OpenPopup("Keybinds"); + ImGui::SetKeyboardFocusHere(); + } break; + default: + { + Assert(0); + } + } + State->ImGuiPopups = popup_none; + + if (ImGui::BeginPopupModal("Save as")) { + ImGui::Text("Destination path..."); + ImGui::InputText("File", State->Filename, 512); + if (ImGui::Button("Save file")) { + ImGui::Text("Saving..."); + File_SaveAs(File, State, Memory, State->Filename); + ImGui::CloseCurrentPopup(); + } + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + if (ImGui::BeginPopupModal("Keybinds")) { + ImGui_KeybindUI(File, State, UI, Memory, io); + } +} + +static void +ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_file Sorted) +{ + if (ImGui::IsKeyPressed(ImGuiKey_Q)) { + State->IsRunning = false; + } + if (ImGui::IsKeyPressed(ImGuiKey_W)) { + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + State->Frame_Current = ((State->Frame_Current - 1) < 0) ? 0 : State->Frame_Current - 1; + State->UpdateFrame = true; + State->UpdateKeyframes = true; + } + if (ImGui::IsKeyPressed(ImGuiKey_E)) { + if (!io.KeyShift) { + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + State->Frame_Current = ((State->Frame_Current + 1) >= MainComp->Frame_Count) ? 0 : State->Frame_Current + 1; + State->UpdateFrame = true; + State->UpdateKeyframes = true; + } else { + State->Brush.EraseMode ^= 1; + } + } + if (ImGui::IsKeyPressed(ImGuiKey_U)) { + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected) { + Layer_ToggleAllChannels(State, Memory, Layer, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyStart, Sorted.PropertyArray); + } + } + } + if (ImGui::IsKeyPressed(ImGuiKey_X)) { + if (State->TimelineMode == timeline_mode_graph && State->Interact_Active == interact_type_keyframe_move) { + if (State->Interact_Modifier != 1) + State->Interact_Modifier = 1; + else + State->Interact_Modifier = 0; + } else { + v4 Temp = UI->Color; + UI->Color = UI->AltColor; + UI->AltColor = Temp; + } + } + if (ImGui::IsKeyPressed(ImGuiKey_Y)) { + if (State->TimelineMode == timeline_mode_graph && State->Interact_Active == interact_type_keyframe_move) { + if (State->Interact_Modifier != 2) + State->Interact_Modifier = 2; + else + State->Interact_Modifier = 0; + } + } + if (ImGui::IsKeyPressed(ImGuiKey_V)) { + State->Tool = tool_default; + } + if (ImGui::IsKeyPressed(ImGuiKey_B)) { + State->Tool = tool_brush; + } + // NOTE(fox): File data not tracked on undo tree! + if (ImGui::IsKeyPressed(ImGuiKey_N)) { + if (io.KeyShift) { + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + if (MainComp->Frame_Start < State->Frame_Current) + MainComp->Frame_End = State->Frame_Current; + } else { + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + if (MainComp->Frame_End > State->Frame_Current) + MainComp->Frame_Start = State->Frame_Current; + } + } + if (ImGui::IsKeyPressed(ImGuiKey_Tab)) { + State->TimelineMode = (State->TimelineMode == timeline_mode_default) ? timeline_mode_graph : timeline_mode_default; + UI->GraphZoomSize = ImVec2(0, 0); + UI->GraphMoveSize = ImVec2(0, 0); + } + if (!io.KeyCtrl) { + // NOTE(fox): Checking IsWindowHovered seems to be all we need to do to + // make per-window hotkeys work; setting it as the focused window causes + // problems with popups. + if (State->FocusedWindow == focus_timeline) { + if (State->TimelineMode == timeline_mode_default) { + if (ImGui::IsKeyPressed(ImGuiKey_G)) { + Layer_ToggleChannel(File, Memory, 0); + Layer_ToggleChannel(File, Memory, 1); + } else if (ImGui::IsKeyPressed(ImGuiKey_A)) { + Layer_ToggleChannel(File, Memory, 2); + Layer_ToggleChannel(File, Memory, 3); + } else if (ImGui::IsKeyPressed(ImGuiKey_R)) { + Layer_ToggleChannel(File, Memory, 4); + } else if (ImGui::IsKeyPressed(ImGuiKey_S)) { + Layer_ToggleChannel(File, Memory, 5); + } else if (ImGui::IsKeyPressed(ImGuiKey_T)) { + if (io.KeyShift) { + Layer_ToggleChannel(File, Memory, 6); + } else { + Layer_ToggleChannel(File, Memory, 7); + } + } + } else if (State->TimelineMode == timeline_mode_graph) { + if (ImGui::IsKeyPressed(ImGuiKey_G)) { + State->Interact_Offset[2] = io.MousePos.x; + State->Interact_Offset[3] = io.MousePos.y; + State->Interact_Active = interact_type_keyframe_move; + } else if (ImGui::IsKeyPressed(ImGuiKey_R)) { + // State->Interact_Offset[2] = io.MousePos.x; + // State->Interact_Offset[3] = io.MousePos.y; + // State->Interact_Active = interact_type_keyframe_rotate; + } else if (ImGui::IsKeyPressed(ImGuiKey_S)) { + State->Interact_Offset[2] = io.MousePos.x; + State->Interact_Offset[3] = io.MousePos.y; + State->Interact_Active = interact_type_keyframe_scale; + } + } + } else if (State->FocusedWindow == focus_viewport) { + if (ImGui::IsKeyPressed(ImGuiKey_T)) { + Interact_Transform_Begin(File, Memory, State, io.MousePos, Sorted.CompArray, Sorted.LayerArray); + State->Tool = tool_default; + } + } + } + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + if (State->Interact_Active == interact_type_keyframe_move || + State->Interact_Active == interact_type_keyframe_rotate || + State->Interact_Active == interact_type_keyframe_scale) { + State->Interact_Offset[0] = 0; + State->Interact_Offset[1] = 0; + State->Interact_Offset[2] = 0; + State->Interact_Offset[3] = 0; + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + State->UpdateFrame = true; + } + } + if (ImGui::IsKeyPressed(ImGuiKey_Space) ) { + if (io.KeyShift) { + State->RerouteEffects = true; + } else { + State->IsPlaying ^= 1; + State->HotFramePerf = 1; + State->PlayAudio = false; + State->FramePlayingCount = 0; + switch (SDL_GetAudioStatus()) + { + case SDL_AUDIO_STOPPED: Assert(0); break; + case SDL_AUDIO_PLAYING: SDL_PauseAudio(1); break; + case SDL_AUDIO_PAUSED: SDL_PauseAudio(0); break; + default: Assert(0); break; + } + } + } + if (ImGui::IsKeyPressed(ImGuiKey_2)) { + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected && Layer->IsPrecomp) { + Layer->Precomp_Toggled ^= 1; + } + } + } + + if (ImGui::IsKeyPressed(ImGuiKey_Delete)) + { + State->HotkeyInput = hotkey_deletelayer; + } + + if (io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_Slash)) + { + State->ImGuiPopups = popup_keybinds; + } + +#if DEBUG + if (ImGui::IsKeyPressed(ImGuiKey_3)) + { + // State->ImGuiPopups = popup_keybinds; + Debug.DisableAlpha = 0; + State->UpdateFrame = true; + } + if (ImGui::IsKeyPressed(ImGuiKey_4)) + { + Debug.DisableAlpha = 1; + State->UpdateFrame = true; + } + if (ImGui::IsKeyPressed(ImGuiKey_5)) + { + Debug.DisableAlpha = 2; + State->UpdateFrame = true; + } + if (ImGui::IsKeyPressed(ImGuiKey_F)) + { + sprintf(State->DummyName, "test2"); + File_Open(File, State, Memory, State->DummyName); + State->UpdateFrame = true; + State->MostRecentlySelectedLayer = 0; + } + if (ImGui::IsKeyPressed(ImGuiKey_0)) + { + Debug.ReloadUI ^= 1; + } + if (ImGui::IsKeyPressed(ImGuiKey_1)) + { + Debug.ToggleWindow ^= 1; + } +#endif + + bool32 mod_key = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + if (mod_key) { + if (ImGui::IsKeyPressed(ImGuiKey_S)) { + if (io.KeyShift) { + State->ImGuiPopups = popup_saveas; + } else { + if (State->Filename[0] == '\0') { + State->ImGuiPopups = popup_saveas; + } else { + File_SaveAs(File, State, Memory, State->Filename); + } + } + } + if (ImGui::IsKeyPressed(ImGuiKey_C)) { + Clipboard_Store(File, State, Memory, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyStart, Sorted.PropertyArray); + } + if (ImGui::IsKeyPressed(ImGuiKey_V)) { + Clipboard_Paste(File, State, Memory, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyArray); + } + if (ImGui::IsKeyPressed(ImGuiKey_Z)) { + if (io.KeyShift) { + State->HotkeyInput = hotkey_redo; + } else { + State->HotkeyInput = hotkey_undo; + } + } + } +} + +static void +ImGui_Menu(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) +{ + bool open = true; + ImGui::Begin("Menu", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar); + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Save", "Ctrl+S")) + { + if (State->Filename[0] == '\0') + State->ImGuiPopups = popup_saveas; + else + File_SaveAs(File, State, Memory, State->Filename); + } + if (ImGui::MenuItem("Save as", "Ctrl+Shift+S")) + { + State->ImGuiPopups = popup_saveas; + } + if (ImGui::BeginMenu("Open file")) + { + ImGui::InputText("Filename", State->DummyName, 512); + if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { + if (File_Open(File, State, Memory, State->DummyName)) { + State->UpdateFrame = true; + } else { + PostMsg(State, "File not found."); + } + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Layer")) + { + if (ImGui::BeginMenu("Import source from file")) + { + ImGui::InputText("Path to image", State->DummyName2, 512); + if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { + int SourceIndex = Source_Generate(File, State, Memory, (void *)State->DummyName2); + State->UpdateFrame = true; + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Window")) + { +#if STABLE + if (ImGui::Selectable("Stable Diffusion tools", UI->StableEnabled)) + UI->StableEnabled ^= 1; + ImGui::EndMenu(); +#endif + } + ImGui::EndMenuBar(); + } + + ImGui::End(); +} + +static void +ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io) +{ + ImGui::Begin("Effects list", NULL); + if (State->RerouteEffects) { + ImGui::SetKeyboardFocusHere(); + State->RerouteEffects = 0; + } + int value_changed = ImGui::InputText("Effect name...", State->filter.InputBuf, IM_ARRAYSIZE(State->filter.InputBuf), + ImGuiInputTextFlags_CallbackCompletion, EffectConsoleCallback); + + if (Hacko) { + if (!io.KeyShift) + EffectSel++; + else + EffectSel--; + Hacko = 0; + } + if (value_changed) { + State->filter.Build(); + EffectSel = -1; + } + // Enter conveniently deactivates the InputText field + if (ImGui::IsItemDeactivated() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { + int32 p = 0; + for (int32 i = 0; i < State->Playhead_Effect; i++) { + header_effect *EffectHeader = &State->Effect[i]; + if (State->filter.PassFilter(EffectHeader->Name)) { + if (EffectSel == p && State->MostRecentlySelectedLayer != -1) { + Effect_Add(File, State, Memory, i); + State->UpdateFrame = true; + } + p++; + } + } + EffectSel = -1; + } + int32 p = 0; + for (int32 i = 0; i < State->Playhead_Effect; i++) { + header_effect *EffectHeader = &State->Effect[i]; + if (State->filter.PassFilter(EffectHeader->Name)) { + bool t = false; + if (EffectSel == p) { + t = true; + } + ImGui::Selectable(EffectHeader->Name, &t); + if (ImGui::IsItemClicked()) { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && State->MostRecentlySelectedLayer != -1) { + Effect_Add(File, State, Memory, i); + State->UpdateFrame = true; + } + } + p++; + } + } + ImGui::End(); +} + +static char ImGuiPrefs[] = "[Window][DockSpaceViewport_11111111]\n" +"Pos=0,0\n" +"Size=3840,2160\n" +"Collapsed=0\n" +"\n" +"[Window][Debug##Default]\n" +"Pos=122,442\n" +"Size=400,400\n" +"Collapsed=0\n" +"\n" +"[Window][Viewport]\n" +"Pos=443,34\n" +"Size=2872,1565\n" +"Collapsed=0\n" +"DockId=0x00000010,0\n" +"\n" +"[Window][###Properties]\n" +"Pos=0,34\n" +"Size=441,1565\n" +"Collapsed=0\n" +"DockId=0x0000000B,0\n" +"\n" +"[Window][Timeline]\n" +"Pos=0,1601\n" +"Size=3840,559\n" +"Collapsed=0\n" +"DockId=0x0000000A,0\n" +"\n" +"[Window][Dear ImGui Demo]\n" +"Pos=2677,34\n" +"Size=523,437\n" +"Collapsed=0\n" +"DockId=0x00000011,1\n" +"\n" +"[Window][Files]\n" +"Pos=3317,604\n" +"Size=523,743\n" +"Collapsed=0\n" +"DockId=0x00000007,0\n" +"\n" +"[Window][Effects list]\n" +"Pos=3317,1349\n" +"Size=523,250\n" +"Collapsed=0\n" +"DockId=0x00000008,0\n" +"\n" +"[Window][Graph editor]\n" +"Pos=0,949\n" +"Size=3200,526\n" +"Collapsed=0\n" +"DockId=0x00000009,0\n" +"\n" +"[Window][undotree]\n" +"Pos=2677,473\n" +"Size=523,572\n" +"Collapsed=0\n" +"DockId=0x00000007,2\n" +"\n" +"[Window][memoryviewer]\n" +"Pos=50,273\n" +"Size=800,200\n" +"Collapsed=0\n" +"\n" +"[Window][Example: Custom rendering]\n" +"Pos=758,789\n" +"Size=485,414\n" +"Collapsed=0\n" +"\n" +"[Window][Memory viewer]\n" +"Pos=2677,473\n" +"Size=523,572\n" +"Collapsed=0\n" +"DockId=0x00000007,1\n" +"\n" +"[Window][Graph info]\n" +"Pos=2838,1265\n" +"Size=235,353\n" +"Collapsed=0\n" +"\n" +"[Window][Properties]\n" +"Pos=0,34\n" +"Size=495,1056\n" +"Collapsed=0\n" +"DockId=0x0000000F,0\n" +"\n" +"[Window][Colors]\n" +"Pos=3317,34\n" +"Size=523,568\n" +"Collapsed=0\n" +"DockId=0x00000011,0\n" +"\n" +"[Window][Menu]\n" +"Pos=0,0\n" +"Size=3840,32\n" +"Collapsed=0\n" +"DockId=0x0000000D,0\n" +"\n" +"[Window][Stable Diffusion]\n" +"Pos=2206,684\n" +"Size=421,462\n" +"Collapsed=0\n" +"\n" +"[Window][SD prompt input]\n" +"Pos=2677,473\n" +"Size=523,541\n" +"Collapsed=0\n" +"DockId=0x00000007,2\n" +"\n" +"[Window][Example: Console]\n" +"Pos=747,851\n" +"Size=520,600\n" +"Collapsed=0\n" +"\n" +"[Window][SD gallery]\n" +"Pos=0,718\n" +"Size=441,557\n" +"Collapsed=0\n" +"DockId=0x0000000C,0\n" +"\n" +"[Window][Save as]\n" +"Pos=1782,1058\n" +"Size=300,116\n" +"Collapsed=0\n" +"\n" +"[Window][Keybinds]\n" +"Pos=1573,769\n" +"Size=750,639\n" +"Collapsed=0\n" +"\n" +"[Table][0x861D378E,3]\n" +"Column 0 Weight=1.0000\n" +"Column 1 Weight=1.0000\n" +"Column 2 Weight=1.0000\n" +"\n" +"[Table][0x1F146634,3]\n" +"RefScale=13\n" +"Column 0 Width=63\n" +"Column 1 Width=63\n" +"Column 2 Width=63\n" +"\n" +"[Table][0x64418101,3]\n" +"RefScale=13\n" +"Column 0 Width=63\n" +"Column 1 Width=63\n" +"Column 2 Width=63\n" +"\n" +"[Table][0xC9935533,3]\n" +"Column 0 Weight=1.0000\n" +"Column 1 Weight=1.0000\n" +"Column 2 Weight=1.0000\n" +"\n" +"[Docking][Data]\n" +"DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,0 Size=3840,2160 Split=Y Selected=0x13926F0B\n" +" DockNode ID=0x0000000D Parent=0x8B93E3BD SizeRef=3200,32 HiddenTabBar=1 Selected=0xA57AB2C6\n" +" DockNode ID=0x0000000E Parent=0x8B93E3BD SizeRef=3200,1299 Split=Y\n" +" DockNode ID=0x00000001 Parent=0x0000000E SizeRef=3200,1205 Split=X Selected=0x13926F0B\n" +" DockNode ID=0x00000003 Parent=0x00000001 SizeRef=441,1171 Split=Y Selected=0xDBB8CEFA\n" +" DockNode ID=0x0000000B Parent=0x00000003 SizeRef=521,425 Selected=0xDBB8CEFA\n" +" DockNode ID=0x0000000C Parent=0x00000003 SizeRef=521,347 Selected=0x56290987\n" +" DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1690,1171 Split=X Selected=0x13926F0B\n" +" DockNode ID=0x00000005 Parent=0x00000004 SizeRef=1165,1171 Split=X Selected=0x13926F0B\n" +" DockNode ID=0x0000000F Parent=0x00000005 SizeRef=495,856 Selected=0x199AB496\n" +" DockNode ID=0x00000010 Parent=0x00000005 SizeRef=2199,856 CentralNode=1 Selected=0x13926F0B\n" +" DockNode ID=0x00000006 Parent=0x00000004 SizeRef=523,1171 Split=Y Selected=0x86FA2F90\n" +" DockNode ID=0x00000011 Parent=0x00000006 SizeRef=483,437 Selected=0xBF7DFDC9\n" +" DockNode ID=0x00000012 Parent=0x00000006 SizeRef=483,766 Split=Y Selected=0x59A2A092\n" +" DockNode ID=0x00000007 Parent=0x00000012 SizeRef=523,572 Selected=0x86FA2F90\n" +" DockNode ID=0x00000008 Parent=0x00000012 SizeRef=523,192 Selected=0x812F222D\n" +" DockNode ID=0x00000002 Parent=0x0000000E SizeRef=3200,559 Split=Y Selected=0x0F18B61B\n" +" DockNode ID=0x00000009 Parent=0x00000002 SizeRef=3250,526 Selected=0xA1F22F4D\n" +" DockNode ID=0x0000000A Parent=0x00000002 SizeRef=3250,323 HiddenTabBar=1 Selected=0x0F18B61B\n" +"\0"; diff --git a/src/imgui_ui_debug.cpp b/src/imgui_ui_debug.cpp new file mode 100644 index 0000000..f79fb88 --- /dev/null +++ b/src/imgui_ui_debug.cpp @@ -0,0 +1,76 @@ +static void +ImGui_DebugMemoryViewer(memory *Memory, project_state *State) +{ + ImGui::Begin("Memory viewer"); + + ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); + ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); + ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); + + // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + // ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + + cache_entry *EntryArray = State->Render.Entry; + char *Type[4] = { "unassigned", "comp", "source", "layer" }; + uint32 Blocks_Total = Memory->Slot[B_CachedBitmaps].Size / BitmapBlockSize; + uint32 PerRow = sqrt(Blocks_Total); + real32 BlockSize = ViewportScale.x / PerRow; + for (int c = 0; c < Memory->EntryCount; c++) { + ImGui::PushID(c); + cache_entry Entry = EntryArray[c]; + uint32 BlockSpan; + if (c+1 != Memory->EntryCount) { + cache_entry NextEntry = EntryArray[c+1]; + BlockSpan = NextEntry.Block_StartIndex - Entry.Block_StartIndex; + } else { + BlockSpan = 1; + } + ImVec2 ButtonPos = ViewportMin + ImVec2((Entry.Block_StartIndex % PerRow) * BlockSize, BlockSize * (Entry.Block_StartIndex / PerRow)); + ImVec2 ButtonSize = ImVec2(BlockSpan * BlockSize, BlockSize); + ImGui::SetCursorScreenPos(ButtonPos); + char size[20]; + sprintf(size, "%lu##uimemoryblock", EntryArray[c].CycleTime); + if (ButtonPos.x + ButtonSize.x > ViewportMax.x) { + real32 ButtonSizeSplit = ViewportMax.x - ButtonPos.x; + ImGui::Button(size, ImVec2(ButtonSizeSplit, ButtonSize.y)); + ImVec2 ButtonPos2 = ImVec2(ViewportMin.x, ButtonPos.y + BlockSize); + ImGui::SetCursorScreenPos(ButtonPos2); + ImGui::Button("##uimemoryblockpad", ImVec2(ButtonSize.x - ButtonSizeSplit, ButtonSize.y)); + } else { + ImGui::Button(size, ButtonSize); + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("Type - %s, Start - %i, Info - %i, %i", Type[EntryArray[c].Type], EntryArray[c].Block_StartIndex, EntryArray[c].TypeInfo, EntryArray[c].TypeInfo_Sub); + ImGui::EndTooltip(); + } + ImGui::PopID(); + } + // ImGui::PopStyleVar(2); + ImGui::End(); +} + +static void +ImGui_DebugRenderQueue(project_state *State) +{ + ImGui::Begin("debug_queue"); + for (int i = 0; i < State->Queue.CurrentIdx; i++) { + v2 Pos = State->Queue.Item[i].Pos; + char size[20]; + sprintf(size, "Type %i: %.2f, %.2f", State->Queue.Item[i].Type, Pos.x, Pos.y); + ImGui::MenuItem(size, NULL, (i == State->Queue.Playhead)); + } + ImGui::End(); +} + +static void +ImGui_DebugUndoTree(memory *Memory, project_state *State) +{ + ImGui::Begin("debug_undotree"); + for (int i = 0; i < Memory->History.NumberOfEntries; i++) { + history_entry Entry = Memory->History.Entry[i]; + bool32 CurrentPos = (i < Memory->History.EntryPlayhead); + ImGui::MenuItem(Entry.Name, NULL, CurrentPos); + } + ImGui::End(); +} diff --git a/src/imgui_ui_properties.cpp b/src/imgui_ui_properties.cpp new file mode 100644 index 0000000..5fda218 --- /dev/null +++ b/src/imgui_ui_properties.cpp @@ -0,0 +1,441 @@ +static void +ImGui_Properties_RGBASwitch(project_state *State, memory *Memory, ImGuiIO io, uint32 *Channel) +{ + char *Names[5] = {"All", "R", "G", "B", "A" }; + if (ImGui::BeginListBox("RGB")) { + for (int i = 0; i < 5; i++) { + if (ImGui::Selectable(Names[i], (*Channel == i))) { + *Channel = i; + } + } + ImGui::EndListBox(); + } +} + +static void +ImGui_Properties_Slider(project_state *State, memory *Memory, property_channel *Property, ImGuiIO &io, ImVec2 WindowMinAbs, ImVec2 WindowMaxAbs, memory_table_list Table) +{ + if (ImGui::IsItemActive()) { + State->UpdateFrame = true; + // ImGui_WarpMouse(State, io.MousePos, WindowMinAbs, WindowMaxAbs, 1); + } + + if (ImGui::IsItemActivated()) { + State->Interact_Offset[0] = Property->CurrentValue; + State->Interact_Active = interact_type_slider_scrub; + } + if (ImGui::IsItemDeactivatedAfterEdit()) { + // Pressing Esc while dragging a slider conveniently stops the input in + // ImGui, so all we need to do is set it back: + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + Property->CurrentValue = State->Interact_Offset[0]; + } else if (!Property->Keyframe_Count) { + History_Entry_Commit(Memory, "Property interact"); + real32 Temp = Property->CurrentValue; + Property->CurrentValue = State->Interact_Offset[0]; + History_Action_Swap(Memory, Table, sizeof(Property->CurrentValue), &Property->CurrentValue); + Property->CurrentValue = Temp; + History_Entry_End(Memory); + } + State->Interact_Active = interact_type_none; + // State->UpdateFrame = true; + // State->Warp_X = 0; + // State->Warp_Y = 0; + } +} + + +static void +ImGui_Properties_CurvesUI(project_state *State, memory *Memory, ImGuiIO io, block_effect *Effect, property_channel *PropertyStart, uint16 *SortedPointStart) +{ + + real32 Padding = ImGui::GetFontSize()*6; + ImVec2 ViewportMin = ImGui::GetCursorScreenPos() + Padding/6; + ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); + ViewportScale.y = ViewportScale.x = ViewportScale.x - Padding; // square seems nice + ImVec2 ViewportMax = ViewportMin + ViewportScale; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); + draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); + + real32 PointSize = 40; + + ImU32 col = ImGui::GetColorU32(ImGuiCol_Text); + ImU32 col_light = ImGui::GetColorU32(ImGuiCol_TextDisabled); + + ImVec2 Point_ScreenPos[4]; + + // ocd? + draw_list->PushClipRect(ViewportMin + 2, ViewportMax - 2, true); + + for (real32 i = 0.25; i < 1.0; i += 0.25) { + ImVec2 Horizontal = ViewportMin + ViewportScale * ImVec2(0, i); + ImVec2 Vertical = ViewportMin + ViewportScale * ImVec2(i, 0); + draw_list->AddLine(Horizontal, Horizontal + ImVec2(ViewportScale.x, 0), col_light, 1.0f); + draw_list->AddLine(Vertical, Vertical + ImVec2(0, ViewportScale.y), col_light, 1.0f); + } + + draw_list->PopClipRect(); + + uint32 *SelectedChannel = (uint32 *)&Effect->ExtraData[5]; + real32 *Num = &Effect->ExtraData[*SelectedChannel]; + + v2 Pos = {}; + bool32 AddPoint = 0; + + for (uint32 i = 0; i < *(uint32 *)Num; i += 1) { + + v2 Point_P1 = Effect_V2(Memory, Effect, SortedPointStart[i]); + v2 Point_P2 = Effect_V2(Memory, Effect, SortedPointStart[i + 1]); + v2 Point_P0 = (i != 0) ? Effect_V2(Memory, Effect, SortedPointStart[i - 1]) : V2(0, 0); + v2 Point_P3 = (i != (*Num - 2)) ? Effect_V2(Memory, Effect, SortedPointStart[i + 2]) : V2(1, 1); + + ImVec2 Point_P0_ScreenPos = ViewportMin + (ImVec2(Point_P0.x, 1.0f - Point_P0.y) * ViewportScale); + ImVec2 Point_P1_ScreenPos = ViewportMin + (ImVec2(Point_P1.x, 1.0f - Point_P1.y) * ViewportScale); + ImVec2 Point_P2_ScreenPos = ViewportMin + (ImVec2(Point_P2.x, 1.0f - Point_P2.y) * ViewportScale); + ImVec2 Point_P3_ScreenPos = ViewportMin + (ImVec2(Point_P3.x, 1.0f - Point_P3.y) * ViewportScale); + + ImGui::PushID(&PropertyStart[SortedPointStart[i]]); + + draw_list->AddNgon(Point_P1_ScreenPos, 2, col, 8, 5.0f); + + ImGui::SetCursorScreenPos(Point_P1_ScreenPos - ImVec2(PointSize/2, PointSize/2)); + ImGui::InvisibleButton("##point", ImVec2(PointSize, PointSize), ImGuiButtonFlags_MouseButtonLeft); + + if (ImGui::IsItemHovered()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + + if (ImGui::IsItemActivated()) { + if (io.KeyCtrl && *Num != 2) { + History_Entry_Commit(Memory, "Curves delete point"); + property_channel *Property_X = Effect_Property(Memory, Effect, SortedPointStart[i]); + History_Action_Swap(Memory, F_Properties, sizeof(Property_X->Identifier), &Property_X->Identifier); + Property_X->Identifier = -1; + property_channel *Property_Y = Effect_Property(Memory, Effect, SortedPointStart[i]+1); + History_Action_Swap(Memory, F_Properties, sizeof(Property_Y->Identifier), &Property_Y->Identifier); + Property_Y->Identifier = -1; + History_Action_Swap(Memory, F_Effects, sizeof(Effect->ExtraData[*SelectedChannel]), &Effect->ExtraData[*SelectedChannel]); + Effect->ExtraData[*SelectedChannel]--; + History_Entry_End(Memory); + } + } + + if (ImGui::IsItemActive()) { + if (io.MouseDelta.x || io.MouseDelta.y) { + property_channel *Property_X = Effect_Property(Memory, Effect, SortedPointStart[i]); + property_channel *Property_Y = Effect_Property(Memory, Effect, SortedPointStart[i]+1); + v2 Point = V2(((io.MousePos - ViewportMin) / ViewportScale)); + if (State->Interact_Active == interact_type_none) { + State->Interact_Active = interact_type_slider_scrub; + State->Interact_Offset[0] = Property_X->CurrentValue; + State->Interact_Offset[1] = Property_Y->CurrentValue; + } + Point.y = 1.0f - Point.y; + Point.x = Normalize(Point.x); + Point.y = Normalize(Point.y); + Property_X->CurrentValue = Point.x; + Property_Y->CurrentValue = Point.y; + State->UpdateFrame = true; + } + } + + if (ImGui::IsItemDeactivated()) { + if (State->Interact_Active == interact_type_slider_scrub) { + History_Entry_Commit(Memory, "Curves change point pos"); + property_channel *Property[2] = { Effect_Property(Memory, Effect, SortedPointStart[i]), + Effect_Property(Memory, Effect, SortedPointStart[i]+1) }; + for (int a = 0; a < 2; a++) + { + real32 Temp = Property[a]->CurrentValue; + Property[a]->CurrentValue = State->Interact_Offset[a]; + History_Action_Swap(Memory, F_Properties, sizeof(Property[a]->CurrentValue), &Property[a]->CurrentValue); + Property[a]->CurrentValue = Temp; + } + History_Entry_End(Memory); + State->Interact_Active = interact_type_none; + } + } + + if (i == (*Num - 1)) { + ImGui::PopID(); + break; + } + + // Conversion from Catmull-Rom curves to Bezier curves for display, + // referencing https://pomax.github.io/bezierinfo/#catmullconv + + ImVec2 bez_m1 = (Point_P2_ScreenPos - Point_P0_ScreenPos) / (6 * Tau); + ImVec2 bez_m2 = (Point_P3_ScreenPos - Point_P1_ScreenPos) / (6 * Tau); + + ImVec2 Point_Bez[4]; + Point_Bez[0] = Point_P1_ScreenPos; + Point_Bez[1] = Point_P1_ScreenPos + bez_m1; + Point_Bez[2] = Point_P2_ScreenPos - bez_m2; + Point_Bez[3] = Point_P2_ScreenPos; + + draw_list->PushClipRect(ViewportMin, ViewportMax, true); + + draw_list->AddBezierCubic(Point_Bez[0], Point_Bez[1], Point_Bez[2], Point_Bez[3], col, 1.0f, 0); + + if (ImGui::BezierInteractive(Point_Bez[0], Point_Bez[1], Point_Bez[2], Point_Bez[3]) && + io.MousePos.x > (Point_P1_ScreenPos.x + PointSize/2) && + io.MousePos.x < (Point_P2_ScreenPos.x - PointSize/2)) + { + ImGui::SetCursorScreenPos(io.MousePos - ImVec2(5,5)); + ImGui::Button("pointclick", ImVec2(10, 10)); + if (ImGui::IsItemActivated()) { + Pos = V2(((io.MousePos - ViewportMin) / ViewportScale)); + Pos.y = 1.0f - Pos.y; + Pos.x = Normalize(Pos.x); + Pos.y = Normalize(Pos.y); + AddPoint = true; + } + } + + if (i == 0) + draw_list->AddLine(ImVec2(ViewportMin.x, Point_Bez[0].y), Point_Bez[0], col, 1.0f); + if (i == (*Num - 2)) + draw_list->AddLine(ImVec2(ViewportMax.x, Point_Bez[3].y), Point_Bez[3], col, 1.0f); + + draw_list->PopClipRect(); + +#if 0 + for (int x = 0; x < 256; x++) { + v2 Point = V2((real32)x/256, LUT[*ChannelIndex][x]); + ImVec2 Point_ScreenPos = ViewportMin + (ImVec2(Point.x, 1.0f - Point.y) * ViewportScale); + draw_list->AddNgon(Point_ScreenPos, 1, col, 8, 5.0f); + } +#endif + + draw_list->AddNgon(Point_P1_ScreenPos, 2, col, 8, 5.0f); + + + ImGui::PopID(); + } + + + if (AddPoint) { + int x = 0; + History_Entry_Commit(Memory, "Curves add point"); + for (;;) { + if (x > MAX_PROPERTIES_PER_EFFECT) + break; + property_channel *Property_X = Effect_Property(Memory, Effect, x); + if (Property_X->Identifier == -1) { + History_Action_Swap(Memory, F_Properties, sizeof(Property_X->CurrentValue), &Property_X->CurrentValue); + Property_X->CurrentValue = Pos.x; + History_Action_Swap(Memory, F_Properties, sizeof(Property_X->Identifier), &Property_X->Identifier); + Property_X->Identifier = *SelectedChannel; + property_channel *Property_Y = Effect_Property(Memory, Effect, x+1); + Assert(Property_Y->Identifier == -1); + History_Action_Swap(Memory, F_Properties, sizeof(Property_Y->CurrentValue), &Property_Y->CurrentValue); + Property_Y->CurrentValue = Pos.y; + History_Action_Swap(Memory, F_Properties, sizeof(Property_Y->Identifier), &Property_Y->Identifier); + Property_Y->Identifier = *SelectedChannel; + x = MAX_PROPERTIES_PER_EFFECT; + } + x++; + } + History_Action_Swap(Memory, F_Effects, sizeof(Effect->ExtraData[*SelectedChannel]), &Effect->ExtraData[*SelectedChannel]); + Effect->ExtraData[*SelectedChannel]++; + AddPoint = false; + History_Entry_End(Memory); + } + + // ImVec2 ButtonPos = ImGui::GetCursorScreenPos(); + ImGui::SetCursorScreenPos(ViewportMin); + + ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsActive = ImGui::IsItemActive(); + bool32 IsActivated = ImGui::IsItemActivated(); + bool32 IsDeactivated = ImGui::IsItemDeactivated(); + + ImVec2 EndPos = ImGui::GetCursorScreenPos(); + + ImGui::SetCursorScreenPos(ViewportMin + ImVec2(ViewportScale.x + 20, 0)); + ImGui_Properties_RGBASwitch(State, Memory, io, SelectedChannel); + + ImGui::SetCursorScreenPos(EndPos); +} + +static void +ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + bool32 Display = 1; + block_layer *Layer = NULL; + sorted_layer_array *SortedLayer = NULL; + if (State->MostRecentlySelectedLayer > -1) { + Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->MostRecentlySelectedLayer, 0); + sorted_comp_array SortedCompStart = SortedCompArray[Layer->Block_Composition_Index]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, &SortedCompStart, Layer->Block_Composition_Index); + SortedLayer = &SortedLayerStart[State->MostRecentlySelectedLayer]; + if (!Layer->Occupied) + Display = 0; + } else { + Display = 0; + } + if (Display) { + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); + char buf[256]; + sprintf(buf, "Properties: %s###Properties", String->Char); + ImGui::Begin(buf); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) + State->FocusedWindow = focus_properties; + ImVec2 WindowSize = ImGui::GetWindowSize(); + ImVec2 WindowMinAbs = ImGui::GetWindowPos(); + ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; + ImGui::Text("Transform"); + + sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayer->SortedPropertyStart; + uint16 *ArrayLocation = SortedKeyframeArray + SortedLayer->SortedKeyframeStart; + int h = 0, c = 0, p = 0; + property_channel *Property = NULL; + block_effect *Effect = NULL; + while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) + { + ImGui::PushID(Property); + if ((h - 1) < AmountOf(Layer->Property) && c == 0) { + if (ImGui::Button("K")) { + Property_AddKeyframe(Memory, F_Layers, Property, State->Frame_Current, ArrayLocation); + } + ImGui::SameLine(); +#if DEBUG + char size[64]; + sprintf(size, "%s, %i", DefaultChannel[h-1], Property->Keyframe_Count); + char *Name = size; +#else + char *Name = DefaultChannel[h-1]; +#endif + ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); + ImGui_Properties_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Layers); + } else { + Assert(Effect); + header_effect *EffectHeader = Effect_EntryFromID(State, Effect->ID); + header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + c - 1]; +#if DEBUG + char size[64]; + sprintf(size, "%s, %i", ChannelHeader.Name, Property->Keyframe_Count); + char *Name = size; +#else + char *Name = ChannelHeader.Name; +#endif + if ((c - 1) == 0) { + uint16 EffectIdx = Layer->Block_Effect_Index[h - AmountOf(Layer->Property)]; + ImGui::PushID(h); +#if DEBUG + ImGui::Text("%s, %i", EffectHeader->Name, EffectIdx); +#else + ImGui::Text(EffectHeader->Name); +#endif + char T_VisibleIcon[4] = "o -"; + char F_VisibleIcon[4] = "- -"; + char *Icon = (Effect->IsToggled) ? T_VisibleIcon : F_VisibleIcon; + + if (ImGui::Button(Icon)) { + History_Entry_Commit(Memory, "Toggle visibility"); + History_Action_Swap(Memory, F_Effects, sizeof(Effect->IsToggled), &Effect->IsToggled); + Effect->IsToggled ^= 1; + History_Entry_End(Memory); + State->UpdateFrame = true; + } + + ImGui::SameLine(); + if (ImGui::Button("/\\")) { + if (EffectIdx != 0) { + } + } + ImGui::SameLine(); + if (ImGui::Button("\\/")) { + } + + ImGui::PopID(); + } + if (EffectHeader->DisplayType == effect_display_type_standard) { + if (ChannelHeader.DisplayType == property_display_type_standard) { + if (ImGui::Button("K")) { + Property_AddKeyframe(Memory, F_Properties, Property, State->Frame_Current, ArrayLocation); + } + ImGui::SameLine(); + ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); + ImGui_Properties_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Properties); + } else if (ChannelHeader.DisplayType == property_display_type_color) { + if (ImGui::Button("K")) { + Property_AddKeyframe(Memory, F_Properties, Property, State->Frame_Current, ArrayLocation); + } + ImGui::SameLine(); + ImGui::DragScalar(Name, ImGuiDataType_Float, &Property->CurrentValue, Property->ScrubVal, &Property->MinVal, &Property->MaxVal, "%f"); + ImGui_Properties_Slider(State, Memory, Property, io, WindowMinAbs, WindowMaxAbs, F_Properties); + // if (c == 3) { + // ImGui::ColorEdit4("col", Col, ImGuiColorEditFlags_Float); + // } + } else { + Assert(0); + } + } else if (EffectHeader->DisplayType == effect_display_type_curves) { +#if DEBUG + ImGui::Text("Points (RGBA): %.02f, Points (indiv): %.02f, %.02f, %.02f, %.02f", Effect->ExtraData[0], + Effect->ExtraData[1], Effect->ExtraData[2], Effect->ExtraData[3], Effect->ExtraData[4]); +#endif + if (Property->Identifier == -1) { + Effect_Curves_Init(Effect, Property); + } + uint16 SortedPointStart[MAX_PROPERTIES_PER_EFFECT/5]; + uint32 VisibleChannel = *(uint32 *)&Effect->ExtraData[5]; + Effect_Curves_Sort(Memory, Effect, SortedPointStart, VisibleChannel); + ImGui_Properties_CurvesUI(State, Memory, io, Effect, Property, SortedPointStart); + c = EffectHeader->Property_Count; // Causes this loop to only iterate once. + } else if (EffectHeader->DisplayType == effect_display_type_levels) { + ImGui::Text("Levels!"); + uint32 VisibleChannel = *(uint32 *)&Effect->ExtraData[0]; + real32 *P_Left = 0, *P_Mid = 0, *P_Right = 0; + if (VisibleChannel == 0) { + property_channel *Property0 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[0]); + property_channel *Property1 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[1]); + property_channel *Property2 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[2]); + P_Left = &Property0->CurrentValue; + P_Mid = &Property1->CurrentValue; + P_Right = &Property2->CurrentValue; + } else { + property_channel *Property0 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[3+(VisibleChannel-1)]); + property_channel *Property1 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[7+(VisibleChannel-1)]); + property_channel *Property2 = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[11+(VisibleChannel-1)]); + P_Left = &Property0->CurrentValue; + P_Mid = &Property1->CurrentValue; + P_Right = &Property2->CurrentValue; + } + ImGui::SliderLevels("1", "2,", "3", (void *)P_Mid, (void *)P_Left, (void *)P_Right); + if (ImGui::IsItemActive()) { + State->UpdateFrame = true; + } + ImGui_Properties_RGBASwitch(State, Memory, io, (uint32 *)&Effect->ExtraData[0]); + c = EffectHeader->Property_Count; + } else { + Assert(0); + } + } + ImGui::PopID(); + } + if (Layer->IsPrecomp) { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + ImGui::DragScalar("Width", ImGuiDataType_U16, &Comp->Width); + if (ImGui::IsItemActive()) { + State->UpdateFrame = true; + } + ImGui::DragScalar("Height", ImGuiDataType_U16, &Comp->Height); + if (ImGui::IsItemActive()) { + State->UpdateFrame = true; + } + } + ImGui::End(); + } else { + ImGui::Begin("Properties: empty###Properties"); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) + State->FocusedWindow = focus_properties; + ImGui::End(); + } +} diff --git a/src/imgui_ui_stable_diffusion.cpp b/src/imgui_ui_stable_diffusion.cpp new file mode 100644 index 0000000..a5c93c6 --- /dev/null +++ b/src/imgui_ui_stable_diffusion.cpp @@ -0,0 +1,175 @@ +static void +ImGui_SD_Thumbnail(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 *SourceArray, uint16 SourceCount) +{ + ImGui::Begin("SD gallery"); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); + ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); + ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); + if (!SourceCount || ViewportScale.x < 50) { + ImGui::End(); + return; + } + int test[16]; + int count = 0; + // draw_list->AddImage((void *)(intptr_t)textureID, ViewportMin, ViewportMax); + uint32 T_Height = 128; + real32 RowPercent = (real32)ViewportScale.x / T_Height; + uint32 PerRow = (uint32)RowPercent; + uint32 UI_Size = T_Height; + bool32 Active = false; + for (int i = 0; i < SourceCount; i++) { + int32 Y = i / PerRow; + int32 X = i % PerRow; + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, SourceArray[i]); + // ImGui::Text("Count: %i", Source->RelativeTimestamp); + real32 Ratio = (real32)Source->Width / Source->Height; + uint32 T_Width = (uint32)(Ratio * T_Height); + if (Source->ThumbnailTex == 0) { + Source_DumpThumbnail(Memory, Source, T_Width, T_Height); + } + // draw_list->AddRect(ImgMin, ImgMax, IM_COL32(255, 255, 255, 64)); + ImVec2 ImgMin = ViewportMin + ImVec2(X * UI_Size, Y * UI_Size); + ImGui::SetCursorScreenPos(ImgMin); + real32 ClosestSpacing = 0.2; + bool32 Wide = (Source->Width > Source->Height); + // ImVec2 Min = (Wide) ? ImVec2(0,ClosestSpacing) : ImVec2(ClosestSpacing,0); + // ImVec2 Max = (Wide) ? ImVec2(1-ClosestSpacing,1) : ImVec2(1,1-ClosestSpacing); + ImVec2 Min(0.2, 0.2); + ImVec2 Max(0.8, 0.8); + ImGui::ImageButton((void *)(intptr_t)Source->ThumbnailTex, ImVec2(UI_Size, UI_Size), Min, Max, 12); + // ImGui::ImageButton(user_texture_id, & sizeconst ImVec2& = ImVec2(0, 0), = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding + // ImVec2 ImgMax = ImgMin + ImVec2(UI_Size * Ratio, UI_Size); + // ImGui::Button("##thumbnail", ImVec2(UI_Size, UI_Size)); + // draw_list->AddImage((void *)(intptr_t)Source->ThumbnailTex, ImgMin, ImgMax); + + if (ImGui::IsItemHovered()) { + State->PreviewSource = SourceArray[i]; + State->UpdateFrame = true; + Active = true; + block_source *Source1 = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, 1); + block_source *Source2 = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, 2); + block_source *Source0 = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, 0); + real32 a = 0; + } + + if (ImGui::IsItemClicked() || ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (!io.KeyShift && !Source->IsSelected) { + Source_DeselectAll(File, Memory); + } + Source->IsSelected = 1; + } + ImGui::OpenPopupOnItemClick("temptosource", ImGuiPopupFlags_MouseButtonRight); + } + if (State->PreviewSource != -1 && !Active) { + State->PreviewSource = -1; + State->UpdateFrame = true; + } + if (ImGui::BeginPopup("temptosource")) { + if (ImGui::MenuItem("Create layer from source")) { + State->HotkeyInput = hotkey_newlayerfromsource; + } + ImGui::EndPopup(); + } + + ImGui::End(); +} + +static void +ImGui_SD_Prompt(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) +{ + sorted_comp_array *SortedCompStart = &SortedCompArray[File->PrincipalCompIndex]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray,File->PrincipalCompIndex); + ImGui::Begin("SD prompt input"); + sd_state *SD = &File->UI.SD; + int Size = ImGui::GetFontSize(); + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(200, 80, 0, 255)); + if (ImGui::Button("Generate!", ImVec2(Size*8, Size*2))) { + if (!State->CurlActive) { + if (SD->Mode) { + block_layer *Layer = NULL; + for (int i = SortedCompStart->LayerCount - 1; i >= 0; i--) { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *TestLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (TestLayer->IsSelected) { + Layer = TestLayer; + break; + } + } + if (Layer) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + void *BitmapAddress; + if (Source->Type == source_type_principal) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + BitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index, 0); + } + else if (Source->Type == source_type_file) { + cache_entry *Entry = Memory_Cache_Search(State, Memory, cache_entry_type_source, Layer->Block_Source_Index, 0); + Assert(Entry->IsCached); + BitmapAddress = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry->Block_StartIndex); + } + uint64 Size = Source->Width * Source->Height * Source->BytesPerPixel; + + int32 len = 0; + uint8 *PNGBitmap = stbi_write_png_to_mem((uint8 *)BitmapAddress, Source->Width * Source->BytesPerPixel, + Source->Width, Source->Height, Source->BytesPerPixel, &len); + Assert(PNGBitmap); + + uint64 EncodedEstimateSize = base64_encode_size((size_t)Size); + uint8 *EncodedOutput = (uint8 *)Memory_PushScratch(Memory, EncodedEstimateSize); + uint64 EncodedTrueSize = 0; + + base64_encode((uint8 *)PNGBitmap, len, EncodedOutput, (size_t *)&EncodedTrueSize); + Assert(EncodedOutput[EncodedTrueSize] == '\0'); + // printf("%s", EncodedOutput); + + STBIW_FREE(PNGBitmap); + + SD_AssembleJSON(SD, (char *)State->JSONPayload, EncodedOutput); + Memory_PopScratch(Memory, EncodedEstimateSize); + State->CurlActive = -1; + } + } else { + SD_AssembleJSON(SD, State->JSONPayload); + // SD_AssembleJSON(SD, (char *)State->Dump2); + State->CurlActive = -1; + } + } + } + ImGui::PopStyleColor(); + if (State->CurlActive) { + ImGui::SameLine(); + ImGui::Text("Est. time: %.1f sec, %.2f", State->SDTimeEstimate, State->SDPercentDone*100); + } + ImGui::InputText("Address", SD->ServerAddress, SD_LEN_ADDRESS); + if (SD->ServerAddress[0] == '\0' && SD->Prompt[0] == '\0') { + sprintf(SD->Prompt, "%s", "highres"); + sprintf(SD->NegPrompt, "%s", "nsfw, \ntext, \ncropped, \nworst quality, \nlow quality, \nnormal quality, \njpeg artifacts, \nsignature, \nwatermark"); + sprintf(SD->ServerAddress, "%s", "http://127.0.0.1:7860"); + } + if (ImGui::Selectable("txt2img", !SD->Mode)) + SD->Mode = 0; + if (ImGui::Selectable("img2img", SD->Mode)) + SD->Mode = 1; + ImGui::InputTextMultiline("Prompt", SD->Prompt, SD_LEN_PROMPT); + ImGui::InputTextMultiline("Negative prompt", SD->NegPrompt, SD_LEN_PROMPT); + ImGui::SliderInt("Steps", &SD->Steps, 0, 150); + ImGui::SliderInt("Width", &SD->Width, 64, 2048, "%i px"); + if (ImGui::IsItemDeactivatedAfterEdit()) { + SD->Width = SD->Width + (64 - (SD->Width % 64)); + } + ImGui::SliderInt("Height", &SD->Height, 64, 2048, "%i px"); + if (ImGui::IsItemDeactivatedAfterEdit()) { + SD->Height = SD->Height + (64 - (SD->Height % 64)); + } + ImGui::SliderFloat("CFG scale", &SD->CFG, 1, 30, "%.2f"); + if (SD->Mode) { + ImGui::SliderFloat("Denoising strength", &SD->DenoisingStrength, 0, 1, "%.2f"); + } + ImGui::SliderInt("Batch size", &SD->BatchSize, 1, 8, "%i"); + ImGui::InputInt("Seed", &SD->Seed); + ImGui::End(); +} diff --git a/src/imgui_ui_timeline.cpp b/src/imgui_ui_timeline.cpp new file mode 100644 index 0000000..3a89641 --- /dev/null +++ b/src/imgui_ui_timeline.cpp @@ -0,0 +1,1070 @@ +static void +ImGui_Timeline_BGElements(project_data *File, project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp, + ImVec2 TimelineZoomSize, ImVec2 TimelineMoveSize) +{ + // start/end regions + uint32 PreviewAreaColor = IM_COL32(128, 128, 128, 30); + ImVec2 Region_Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)MainComp.Frame_Start / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y); + ImVec2 Region_Max = ImVec2(Region_Min.x + ((real32)(MainComp.Frame_End - MainComp.Frame_Start) / MainComp.Frame_Count)*TimelineZoomSize.x, Region_Min.y + TimelineSizeWithBorder.y); + draw_list->AddRectFilled(Region_Min, Region_Max, PreviewAreaColor); + // cache bar + cache_entry *EntryArray = State->Render.Entry; + int c = 0; + int count = Memory->EntryCount; + while (count != 0) { + if (EntryArray[c].IsCached) { + if (EntryArray[c].Type == cache_entry_type_comp && + EntryArray[c].TypeInfo == File->PrincipalCompIndex) { + int Frame = EntryArray[c].TypeInfo_Sub; + ImVec2 Region_Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)Frame / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y); + ImVec2 Region_Max = ImVec2(Region_Min.x + ((real32)(1) / MainComp.Frame_Count)*TimelineZoomSize.x, Region_Min.y + 2.0f); + draw_list->AddRectFilled(Region_Min, Region_Max, IM_COL32(0, 0, 128, 100)); + } + count--; + } + c++; + } +} + +static void +ImGui_Timeline_HorizontalIncrementDraw(project_state *State, ui *UI, ImDrawList *draw_list, ImVec2 TimelineSizeWithBorder, ImVec2 TimelineAbsolutePos, block_composition MainComp, + ImVec2 TimelineZoomSize, ImVec2 TimelineMoveSize) +{ + uint32 LineColor = IM_COL32(200, 200, 200, 40); + uint32 PlayheadColor = IM_COL32(000, 000, 200, 160); + uint32 PreviewAreaColor = IM_COL32(128, 128, 128, 60); + + Assert(TimelineZoomSize.x > 0.0f); + + real32 x = 0; + bool32 RightmostEdge = false; + real32 Increment = (real32)1 / MainComp.Frame_Count; + if (UI->TimelinePercentZoomed.x > 0.90) + Increment = (real32)MainComp.FPS / MainComp.Frame_Count; + else if (UI->TimelinePercentZoomed.x > 0.40) + Increment *= 2; + + while (!RightmostEdge) { + ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + x*TimelineZoomSize.x, TimelineAbsolutePos.y); + ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y); + if (Min.x < TimelineAbsolutePos.x + TimelineSizeWithBorder.x) { + draw_list->AddLine(Min, Max, LineColor); + char buf2[6]; + uint32 FrameNumber = (uint32)(x * MainComp.Frame_Count) % MainComp.FPS; + if (FrameNumber != 0) + sprintf(buf2, ":%.2i", FrameNumber); + else + sprintf(buf2, "%.2i:00", (uint32)(x * MainComp.Frame_Count) / MainComp.FPS); + draw_list->AddText(ImVec2(Min.x, TimelineAbsolutePos.y), IM_COL32(200, 200, 200, 130), buf2); + x += Increment; + if (x > 1.0f) + RightmostEdge = true; + } else { + RightmostEdge = true; + } + } + + ImVec2 Min = ImVec2(TimelineAbsolutePos.x + TimelineMoveSize.x + ((real32)State->Frame_Current / MainComp.Frame_Count)*TimelineZoomSize.x, TimelineAbsolutePos.y); + ImVec2 Max = ImVec2(Min.x + 2, TimelineAbsolutePos.y + TimelineSizeWithBorder.y); + draw_list->AddLine(Min, Max, PlayheadColor); + + real32 Size = ImGui::GetFontSize() * 2; + ImGui::SetCursorScreenPos(Min - ImVec2(Size / 2, 0)); + ImGui::Button("##playhead", ImVec2(Size, Size)); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsItemActive = ImGui::IsItemActive(); + bool32 IsItemActivated = ImGui::IsItemActivated(); + bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); + if (IsItemActivated) { + State->Interact_Active = interact_type_timeline_scrub; + State->Interact_Offset[0] = State->Frame_Current; + } + if (IsItemActive) { + ImVec2 DragDelta = ImGui::GetMouseDragDelta(); + int32 NewCurrent = (int32)(State->Interact_Offset[0] + (DragDelta.x / TimelineZoomSize.x * MainComp.Frame_Count) + 0.5); + if (NewCurrent != State->Frame_Current) { + State->Frame_Current = NewCurrent; + State->UpdateFrame = true; + } + } + if (IsItemDeactivated) { + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + } +} + +// TODO(fox): Bring back graph info! +static void +ImGui_Timeline_GraphInfo(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + bool open = true; + ImGui::Begin("Graph info"); + + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) + { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (!Layer->IsSelected) + continue; + + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); + ImGui::Text(String->Char); + + ImGui::PushID(i); + for (int h = 0; h < AmountOf(Layer->Property); h++) { + property_channel *Property = &Layer->Property[h]; + if (Property->Block_Bezier_Count) { + sorted_property_array *InfoLocation = Property_GetSortedInfo(SortedPropertyStart, i, h); + uint16 *ArrayLocation = Property_GetSortedArray(SortedKeyframeArray, i, h); + ImGui::PushID(Property); + if (ImGui::Selectable(DefaultChannel[h], Property_IsGraphSelected(Memory, Property, ArrayLocation))) { + File_DeselectAllKeyframes(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray); + for (int p = 0; p < Property->Keyframe_Count; p++) { + int k = ArrayLocation[p]; + bezier_point *Point = Bezier_LookupAddress(Memory, Property, k); + Point->IsSelected = true; + } + State->RecentSelectionType = selection_type_keyframe; + } + ImGui::PopID(); + } + } + + ImGui::PopID(); + } + + ImGui::End(); +} + +static void +ImGui_Timeline_DrawKeySheet(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, property_channel *Property, uint16 *ArrayLocation, + ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 GraphPos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, + ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + ImGui::PushID(Property); + + for (int p = 0; p < Property->Keyframe_Count; p++) { + int k = ArrayLocation[p]; + bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, k); + + v2 PointPos[3]; + Bezier_Interact_Evaluate(State, PointAddress, PointPos); + + real32 LocalPos_Ratio_X = PointPos[0].x * Increment.x; + real32 Keyframe_ScreenPos_X = GraphPos.x + TimelineMoveSize.x + LocalPos_Ratio_X * TimelineZoomSize.x; + + ImVec2 Keyframe_ScreenPos(Keyframe_ScreenPos_X, GraphPos.y); + + if (State->BoxSelect) { + if (ImGui_TestBoxSelection_Point(Keyframe_ScreenPos, io, &PointAddress->IsSelected)) + State->RecentSelectionType = selection_type_keyframe; + } + + ImVec2 ButtonSize(16, 16); + + ImGui::PushID(p); + + ImU32 PointCol = (PointAddress->IsSelected) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f); + ImU32 LineCol = (PointAddress->IsSelected) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f); + ImGui::SetCursorScreenPos(Keyframe_ScreenPos - (ButtonSize * 0.5)); + ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsItemActive = ImGui::IsItemActive(); + bool32 IsItemActivated = ImGui::IsItemActivated(); + bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); + bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); + + if (IsHovered) { + PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f); + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + + if (IsItemActivated) { + PointAddress->IsSelected = 1; + } + + if (IsItemActive) { + if (State->Interact_Active == interact_type_none) { + State->Interact_Offset[2] = io.MousePos.x; + State->Interact_Offset[3] = io.MousePos.y; + State->Interact_Active = interact_type_keyframe_move; + State->Interact_Modifier = 1; // X axis movement only + } else { + Assert(State->Interact_Active == interact_type_keyframe_move); + ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); + ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]); + DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); + if (io.MouseDelta.x || io.MouseDelta.y) { + State->UpdateFrame = true; + } + if (State->Interact_Active == interact_type_keyframe_move) { + State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x) / Increment.x; + State->Interact_Offset[1] = DragDelta.y; + } + } + } + + if (IsItemDeactivated) { + Keyframe_Commit(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray); + } + + draw_list->AddCircleFilled(Keyframe_ScreenPos, 4, PointCol); + + ImGui::PopID(); + } + ImGui::PopID(); +} + +static void +ImGui_Timeline_DrawGraph(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, + ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, + ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + // I'm using the draw splitter here to be able to draw the dots on top of the graph lines. + State->Test.Split(draw_list, 2); + + for (int c = 0; c < File->Comp_Count; c++) { + sorted_comp_array SortedCompStart = SortedCompArray[c]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, &SortedCompStart, c); + for (int i = 0; i < SortedCompStart.LayerCount; i++) { + sorted_layer_array *SortedLayer = &SortedLayerStart[i]; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, SortedLayer->Block_Layer_Index); + if (!Layer->IsSelected) + continue; + + int32 Frame_Start = Layer->Frame_Start; + + ImGui::PushID(i); + + if ((State->Interact_Active == interact_type_keyframe_move || + State->Interact_Active == interact_type_keyframe_rotate || + State->Interact_Active == interact_type_keyframe_scale)) + { + ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder); + ImVec2 DragDelta = io.MousePos - ImVec2(State->Interact_Offset[2], State->Interact_Offset[3]); + DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); + if (io.MouseDelta.x || io.MouseDelta.y) { + State->UpdateFrame = true; + } + if (State->Interact_Active == interact_type_keyframe_move) { + // The Y increment varies between graphs, so we have to do it in the Bezier_EvaluateValue call. + State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x) / Increment.x; + State->Interact_Offset[1] = DragDelta.y; + } else if (State->Interact_Active == interact_type_keyframe_scale) { + State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) / Increment.x; + } else if (State->Interact_Active == interact_type_keyframe_rotate) { + State->Interact_Offset[0] = (DragDelta.x / TimelineZoomSize.x); + /* + real32 Slope_Old = (Keyframe_ScreenPos.y - State->Interact_Offset[3]) / (Keyframe_ScreenPos.x - State->Interact_Offset[2]); + real32 Slope_New = (Keyframe_ScreenPos.y - io.MousePos.y) / (Keyframe_ScreenPos.x - io.MousePos.x); + State->Interact_Offset[0] = atan((Slope_Old - Slope_New) / (1 + Slope_Old * Slope_New)); + */ + } + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + } + + sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayer->SortedPropertyStart; + uint16 *ArrayLocation = SortedKeyframeArray + SortedLayer->SortedKeyframeStart; + int h1 = 0, c1 = 0, p = 0; + property_channel *Property = NULL; + while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, NULL, &h1, &c1, &p)) + { + ImGui::PushID(Property); + if (Property->Block_Bezier_Count) { + real32 MinY, MaxY; + Property_MinMax_Y(Memory, State, Property, InfoLocation, &MinY, &MaxY, 0); + Assert(InfoLocation->MinYIndex < Property->Keyframe_Count); + Assert(InfoLocation->MaxYIndex < Property->Keyframe_Count); + Assert(MaxY >= MinY); + real32 Y_Increment = (MaxY - MinY) ? (1 / (MaxY - MinY)) : 0.5; + + real32 GraphScale = 0; + real32 GraphPos = 0; + if ((1 / Y_Increment) < 5) { + GraphScale = 0.2; + GraphPos = 0.3; + } else if ((1 / Y_Increment) > 700) { + GraphScale = 0.6; + GraphPos = 0.06; + } else { + GraphScale = 0.4; + GraphPos = 0.2; + } + + + real32 GraphMoveHeight = TimelineMoveSize.y + (TimelineZoomSize.y * GraphPos); + real32 GraphZoomHeight = TimelineZoomSize.y * GraphScale; + + bool32 IsGraphSelected = Property_IsGraphSelected(Memory, Property, ArrayLocation); + uint32 GraphCol = IsGraphSelected ? IM_COL32(255, 180, 150, 255) : IM_COL32(255, 255, 255, 70); + + bezier_point *PointAddress[2] = {}; + ImVec2 Keyframe_ScreenPos[6] = {}; + for (int p = 0; p < Property->Keyframe_Count; p++) { + int k = ArrayLocation[p]; + if (Property->Keyframe_Count == 1) + int b = 0; + int Idx = (p % 2); + int NewIdx = Idx * 3; + int OldIdx = (NewIdx == 3) ? 0 : 3; + PointAddress[Idx] = Bezier_LookupAddress(Memory, Property, k); + + v2 PointPos[3]; + Bezier_Interact_Evaluate(State, PointAddress[Idx], PointPos, GraphZoomHeight, Y_Increment); + + ImVec2 Keyframe_LocalPos[3] = { V2(PointPos[0]), V2(PointPos[0] + PointPos[1]), V2(PointPos[0] + PointPos[2]) }; + + ImVec2 Keyframe_LocalPos_Ratio[3]; + for (int b = 0; b < 3; b++) { + Keyframe_LocalPos_Ratio[b] = (Keyframe_LocalPos[b] - ImVec2(0, MinY)) * ImVec2(Increment.x, Y_Increment); + Keyframe_ScreenPos[NewIdx + b] = TimelineAbsolutePos + ImVec2(TimelineMoveSize.x, GraphMoveHeight) + ((ImVec2(1, -1) * Keyframe_LocalPos_Ratio[b] + ImVec2(0, 1)) * ImVec2(TimelineZoomSize.x, GraphZoomHeight)); + } + + if (State->BoxSelect) { + if (ImGui_TestBoxSelection_Point(Keyframe_ScreenPos[NewIdx], io, &PointAddress[Idx]->IsSelected)) + State->RecentSelectionType = selection_type_keyframe; + } + + State->Test.SetCurrentChannel(draw_list, 1); + + ImVec2 ButtonSize(16, 16); + + ImGui::PushID(k); + int Max = PointAddress[Idx]->IsSelected ? 2 : 0; + for (int b = Max; b >= 0; b--) { + ImU32 PointCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.0f, 1.0f) : ImColor(0.1f, 0.1f, 0.1f, 1.0f); + ImU32 LineCol = ((PointAddress[Idx]->IsSelected - 1) == b) ? ImColor(0.8f, 0.5f, 0.5f, 1.0f) : ImColor(0.4f, 0.4f, 0.4f, 1.0f); + ImGui::PushID(b); + ImGui::SetCursorScreenPos(Keyframe_ScreenPos[NewIdx + b] - (ButtonSize * 0.5)); + ImGui::InvisibleButton("##keyframemover", ButtonSize, ImGuiMouseButton_Left); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsItemActive = ImGui::IsItemActive(); + bool32 IsItemActivated = ImGui::IsItemActivated(); + bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); + bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); + + if (IsHovered) { + PointCol = ImColor(1.0f, 0.8f, 0.8f, 1.0f); + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + + if (IsItemActivated) { + PointAddress[Idx]->IsSelected = b+1; + } + + if (b != 0 && PointAddress[Idx]->IsSelected) + draw_list->AddLine(Keyframe_ScreenPos[NewIdx], Keyframe_ScreenPos[NewIdx + b], LineCol, 2.0f); + + if (b == 0) { + draw_list->AddCircleFilled(Keyframe_ScreenPos[NewIdx + b], 4, PointCol); + if (IsGraphSelected) { + char buf[8]; + sprintf(buf, "%.2f", Keyframe_LocalPos[0].y); + draw_list->AddText(Keyframe_ScreenPos[NewIdx + b], 0xFFFFFFFF, buf); + } + } else { + draw_list->AddCircle(Keyframe_ScreenPos[NewIdx + b], 6, PointCol, 0, 2); + } + + ImGui::PopID(); + } + ImGui::PopID(); + + State->Test.SetCurrentChannel(draw_list, 0); + + if (p != 0) { + if (PointAddress[0]->Type == interpolation_type_bezier && PointAddress[1]->Type == interpolation_type_bezier) { + draw_list->AddBezierCubic(Keyframe_ScreenPos[OldIdx], Keyframe_ScreenPos[OldIdx + 2], + Keyframe_ScreenPos[NewIdx + 1], Keyframe_ScreenPos[NewIdx], GraphCol, 1.0f, 0); + } else { + draw_list->AddLine(Keyframe_ScreenPos[0], Keyframe_ScreenPos[3], GraphCol, 1.0f); + } + } + } + } + ImGui::PopID(); + } + ImGui::PopID(); + } + } + State->Test.Merge(draw_list); +} + + +static void +ImGui_Timeline_DrawPrecomp(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, ImDrawList *draw_list, int16 RecursionIdx[MAX_PRECOMP_RECURSIONS], uint32 Recursions, + ImVec2 Increment, ImVec2 TimelineAbsolutePos, ImVec2 TimelineMoveSize, ImVec2 TimelineZoomSize, + ImVec2 TimelineSize, ImVec2 TimelineSizeWithBorder, real32 LayerIncrement, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + uint16 CompIndex = 0; + if (RecursionIdx[Recursions] == -1) { + CompIndex = File->PrincipalCompIndex; + } else { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, RecursionIdx[Recursions]); + CompIndex = Layer->Block_Source_Index; + } + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex); + sorted_comp_array SortedCompStart = SortedCompArray[CompIndex]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); + + ImGui::PushID(CompIndex); + + // NOTE(fox): Layer sorting pre-calculates UI positioning, so the order layers are drawn is arbitrary. + real32 DisplayOffset = 0; + for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + + int32 Frame_Start = Layer->Frame_Start; + int32 Frame_End = Layer->Frame_End; + real32 Vertical_Offset = SortEntry.SortedOffset + DisplayOffset; + + Layer_Evaluate_Display(State, Memory, Layer, SortedPropertyStart, SortedKeyframeArray, SortedLayerArray, SortedCompArray, SortedLayerStart, i, &DisplayOffset); + + if (Layer->IsSelected) + Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Frame_Start, &Frame_End); + + ImVec2 Layer_LocalPos = ImVec2(Frame_Start, Vertical_Offset); + ImVec2 Layer_LocalSize = ImVec2(Frame_End - Frame_Start, Layer->Vertical_Height); + + ImVec2 Layer_LocalPos_Ratio = (Layer_LocalPos * Increment); + ImVec2 Layer_LocalSize_Ratio = Layer_LocalSize * Increment; + ImVec2 Layer_ScreenPos_Min = TimelineAbsolutePos + TimelineMoveSize + (Layer_LocalPos_Ratio * TimelineZoomSize); + ImVec2 Layer_ScreenPos_Max = TimelineAbsolutePos + TimelineMoveSize + ((Layer_LocalPos_Ratio + Layer_LocalSize_Ratio) * TimelineZoomSize); + ImVec2 Layer_ScreenSize = Layer_ScreenPos_Max - Layer_ScreenPos_Min; + + // UI + + ImU32 LayerColor = 0; + ImU32 BorderColor = 0; + LayerColor = ImColor(UI->LayerColors[Layer->ColIndex]); + BorderColor = ImColor(0.2, 0.2, 0.2, 1.0f); + + draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, LayerColor); + draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, BorderColor, 2); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index); + char buf[128]; +#if DEBUG + sprintf(buf, "%s, %i", String->Char, Index_Physical); +#else + sprintf(buf, "%s", String->Char); +#endif + if (UI->TimelinePercentZoomed.y <= 1.0f) + draw_list->AddText(Layer_ScreenPos_Min, 0xFFFFFFFF, buf); + + if (Layer->IsSelected == 1) { + draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.25f, 0.25f, 0.25f, 0.5f), 2); + draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(1.0f, 1.0f, 1.0f, 0.5f), 2); + } else if (Layer->IsSelected == 2) { + // draw_list->AddRectFilled(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.25f, 0.25f, 0.25f, 0.5f), 2); + draw_list->AddRect(Layer_ScreenPos_Min, Layer_ScreenPos_Max, ImColor(0.0f, 0.9f, 0.0f, 0.9f), 4); + } + + // + + // Main interaction + + ImGui::PushID(i); + if (State->TimelineMode == timeline_mode_default) { + + if (State->BoxSelect && State->TimelineMode == timeline_mode_default) { + bool32 Test = 0; + if (io.MouseClickedPos[0].y < io.MousePos.y) + Test = (Layer_ScreenPos_Min.y >= io.MouseClickedPos[0].y && Layer_ScreenPos_Min.y <= io.MousePos.y); + else + Test = (Layer_ScreenPos_Max.y <= io.MouseClickedPos[0].y && Layer_ScreenPos_Max.y >= io.MousePos.y); + + if (io.MouseClickedPos[0].x < io.MousePos.x) + Test &= (Layer_ScreenPos_Max.x >= io.MouseClickedPos[0].x && Layer_ScreenPos_Min.x <= io.MousePos.x); + else + Test &= (Layer_ScreenPos_Min.x <= io.MouseClickedPos[0].x && Layer_ScreenPos_Max.x >= io.MousePos.x); + + if (Test) { + if (!Layer->IsSelected) { + Layer_Select(Memory, State, Index_Physical); + } + } else if (!io.KeyShift) { + Layer->IsSelected = false; + } + } + + + ImVec2 ResizeSize = ImVec2(Increment.x * 0.45 * TimelineZoomSize.x, Layer_ScreenSize.y); + ImVec2 ResizePos[2] = { Layer_ScreenPos_Min, ImVec2(Layer_ScreenPos_Max.x - ResizeSize.x, Layer_ScreenPos_Min.y) }; + for (int b = 0; b < 2; b++) { + ImGui::PushID(b); + ImGui::SetCursorScreenPos(ResizePos[b]); + ImGui::Button("##layer_resize", ResizeSize); + + if (ImGui::IsItemHovered()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + if (ImGui::IsItemActivated()) { + if (!Layer->IsSelected) { + if (!io.KeyShift) Layer_DeselectAll(File, State, Memory); + Layer_Select(Memory, State, Index_Physical); + } + } + if (ImGui::IsItemActive()) { + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { + Assert(Layer->IsSelected); + State->Interact_Active = interact_type_layer_timeadjust; + ImVec2 DragDelta = ImGui::GetMouseDragDelta(); + DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); + + State->Interact_Offset[0] = (DragDelta.x / TimelineSizeWithBorder.x * UI->TimelinePercentZoomed.x) * Comp->Frame_Count; + State->Interact_Offset[1] = b; + } + } + if (ImGui::IsItemDeactivated()) { + if (State->Interact_Active == interact_type_layer_timeadjust) { + History_Entry_Commit(Memory, "Adjust layer timing"); + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected) { + // NOTE(fox): Some data on the tree could be saved here. + History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_Start), &Layer->Frame_Start); + History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_End), &Layer->Frame_End); + Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Layer->Frame_Start, &Layer->Frame_End); + } + } + History_Entry_End(Memory); + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + State->Interact_Offset[0] = 0; + State->Interact_Offset[1] = 0; + } + } + + ImGui::PopID(); + } + + ImGui::SetCursorScreenPos(Layer_ScreenPos_Min); + ImGui::InvisibleButton("##layer_mid", Layer_ScreenSize, ImGuiMouseButton_Left); + + if (ImGui::IsItemActivated()) { + if (!Layer->IsSelected) { + if (!io.KeyShift) Layer_DeselectAll(File, State, Memory); + Layer_Select(Memory, State, Index_Physical); + Layer_Select_RecurseUp(Memory, State, Index_Physical, RecursionIdx, Recursions); + } + } + + ImGui::OpenPopupOnItemClick("layerpopup", ImGuiPopupFlags_MouseButtonRight); + if (ImGui::BeginPopup("layerpopup")) { + if (ImGui::Selectable("Visible")) { + bool32 Commit = false; + int h = 0, z = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected) { + if (!Commit) { + History_Entry_Commit(Memory, "Toggle visibility"); + Commit = true; + } + History_Action_Swap(Memory, F_File, sizeof(Layer->IsVisible), &Layer->IsVisible); + Layer->IsVisible ^= 1; + } + } + if (Commit) { + History_Entry_End(Memory); + State->UpdateFrame = true; + } + } + if (ImGui::MenuItem("Pre-compose layer")) { + Precomp_UICreateButton(File, State, Memory, CompIndex, SortedCompStart, SortedLayerStart); + State->UpdateKeyframes = true; + } + if (ImGui::MenuItem("Duplicate layer")) { + Precomp_UIDuplicate(File, State, Memory, CompIndex, SortedCompStart, SortedLayerStart); + } + if (ImGui::MenuItem("Delete layer")) { + Precomp_UICreateButton(File, State, Memory, CompIndex, SortedCompStart, SortedLayerStart); + State->UpdateKeyframes = true; + } + if (ImGui::BeginMenu("Layer color")) + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + for (int c = 0; c < AmountOf(UI->LayerColors); c++) { + ImGui::PushID(c); + ImGui::PushStyleColor(ImGuiCol_Button, UI->LayerColors[c]); + real32 Size = ImGui::GetFontSize() * 2; + ImVec2 ColSize(Size, Size); + if (ImGui::Button("##test", ColSize)) { + int h = 0, z = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &z, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected) { + Layer->ColIndex = c; + } + } + } + if ((c+1) % 4) { ImGui::SameLine(); } + ImGui::PopStyleColor(); + ImGui::PopID(); + } + ImGui::PopStyleVar(); + ImGui::EndMenu(); + } + uint32 *item_current_idx = (uint32 *)&Layer->BlendMode; // Here we store our selection data as an index. + if (ImGui::BeginListBox("Blend mode")) + { + for (int n = 0; n < IM_ARRAYSIZE(BlendmodeNames); n++) + { + const bool is_selected = (*item_current_idx == n); + if (ImGui::Selectable(BlendmodeNames[n], is_selected)) { + *item_current_idx = n; + State->UpdateFrame = true; + } + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndListBox(); + } + ImGui::EndPopup(); + } + + if (ImGui::IsItemActive()) { + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) { + if (State->Interact_Active == interact_type_none) { + State->Interact_Active = interact_type_layer_move; + // TODO(fox): Selected layers inside precomps will have interactions doubled, + // so I'm forcing interaction to only be with members of the same precomp. + // Could be made more intuitive later. + Layer_RecursiveDeselect(Memory, SortedCompArray, SortedLayerArray, Layer->Block_Composition_Index, File->PrincipalCompIndex); + } + ImVec2 DragDelta = ImGui::GetMouseDragDelta(); + DragDelta = DragDelta + (ImVec2(State->Warp_X, State->Warp_Y) * TimelineSize); + + ImVec2 Offset_Old = ImVec2(State->Interact_Offset[0], State->Interact_Offset[1]); + ImVec2 Offset_New = (DragDelta / TimelineSizeWithBorder * UI->TimelinePercentZoomed) * ImVec2(Comp->Frame_Count, -LayerIncrement); + + // if (((int32)Offset_Old.x != (int32)Offset_New.x) || ((int32)Offset_Old.y != (int32)Offset_New.y)) + State->UpdateFrame = true; + + State->Interact_Offset[0] = Offset_New.x; + State->Interact_Offset[1] = Offset_New.y; + } + } + + if (ImGui::IsItemDeactivated()) { + if (State->Interact_Active == interact_type_layer_move) { + History_Entry_Commit(Memory, "Move layers"); + for (int i = 0; i < SortedCompStart.LayerCount; i++) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + // NOTE(fox): Some data on the tree could be saved here. + History_Action_Swap(Memory, F_File, sizeof(Layer->Vertical_Offset), &Layer->Vertical_Offset); + Layer->Vertical_Offset = SortEntry.SortedOffset; + if (Layer->IsSelected) { + History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_Start), &Layer->Frame_Start); + History_Action_Swap(Memory, F_File, sizeof(Layer->Frame_End), &Layer->Frame_End); + Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Layer->Frame_Start, &Layer->Frame_End); + } + } + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + History_Entry_End(Memory); + } + } + + // Keyframe view + uint32 Channel = 0; + + sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayerStart->SortedPropertyStart; + uint16 *ArrayLocation = SortedKeyframeArray + SortedLayerStart->SortedKeyframeStart; + int h = 0, c = 0, p = 0; + property_channel *Property = NULL; + block_effect *Effect = NULL; + while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) + { + if (Property->IsToggled) { + ImVec2 GraphMinPos = ImVec2(TimelineAbsolutePos.x, Layer_ScreenPos_Min.y + Layer_ScreenSize.y + (Layer_ScreenSize.y * Channel)); + ImVec2 GraphPos = GraphMinPos + ImVec2(0, Layer_ScreenSize.y); + ImVec2 GraphMinBounds = GraphMinPos + ImVec2(0, Layer_ScreenSize.y * 0.5); + ImVec2 GraphMaxBounds = GraphMinBounds + ImVec2(TimelineSizeWithBorder.x, Layer_ScreenSize.y); + uint32 col = (Channel % 2) ? IM_COL32(50, 50, 50, 255) : IM_COL32(50, 50, 50, 128); + draw_list->AddRectFilled(GraphMinBounds, GraphMaxBounds, col); + ImGui_Timeline_DrawKeySheet(File, State, Memory, UI, io, draw_list, Property, ArrayLocation, + Increment, TimelineAbsolutePos, GraphPos, TimelineMoveSize, TimelineZoomSize, + TimelineSize, TimelineSizeWithBorder, LayerIncrement, + SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray); + Channel++; + } + } + } + + // + + // Precomp recursion + + if (Layer->IsPrecomp && Layer->Precomp_Toggled) { + + sorted_layer_array *Precomp_SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, Layer->Block_Source_Index); + sorted_comp_array Precomp_SortedCompStart = SortedCompArray[Layer->Block_Source_Index]; + + block_layer *Layer_Top = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Precomp_SortedLayerStart[Precomp_SortedCompStart.LayerCount - 1].Block_Layer_Index); + block_layer *Layer_Bottom = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Precomp_SortedLayerStart[0].Block_Layer_Index); + real32 SmallestY = Layer_Top->Vertical_Offset; + real32 LargestY = Layer_Bottom->Vertical_Offset; + // Layer_Evaluate_Display(Layer_Top, SortedLayerArray, SortedCompArray, Precomp_SortedLayerStart, Precomp_SortedCompStart.LayerCount - 1, &SmallestY); + // Layer_Evaluate_Display(Layer_Bottom, SortedLayerArray, SortedCompArray, Precomp_SortedLayerStart, 0, &LargestY); + real32 PrecompHeight = LargestY - SmallestY + 2; + + ImVec2 MinClipPos = ImVec2(Layer_ScreenPos_Min.x, Layer_ScreenPos_Max.y); + ImVec2 MaxClipPos = ImVec2(Layer_ScreenPos_Max.x, MinClipPos.y + (PrecompHeight * Increment.y * TimelineZoomSize.y)); + draw_list->AddRectFilled(MinClipPos, MaxClipPos, ImColor(0.2f, 0.2f, 0.2f, 1.0f)); + + + ImVec2 Layer_LocalPos_Screen = Layer_LocalPos_Ratio * TimelineZoomSize; + ImVec2 NestedTimelineAbsolutePos = TimelineAbsolutePos + Layer_LocalPos_Screen - ImVec2(0, SmallestY * Increment.y * TimelineZoomSize.y) + ImVec2(0, Layer_ScreenSize.y * 1.5); + + ImGui::PushClipRect(MinClipPos, MaxClipPos, true); + draw_list->PushClipRect(MinClipPos, MaxClipPos, true); + uint32 RecursionsCount = Recursions+1; + RecursionIdx[RecursionsCount] = Index_Physical; + ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, RecursionIdx, RecursionsCount, + Increment, NestedTimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, + TimelineSize, TimelineSizeWithBorder, LayerIncrement, + SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray); + draw_list->PopClipRect(); + ImGui::PopClipRect(); + + } + + // + + ImGui::PopID(); + } + + ImGui::PopID(); +} + +static void +ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + ImVec2 FramePadding = ImGui::GetStyle().FramePadding; + ImVec2 ItemSpacing = ImGui::GetStyle().ItemSpacing; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // makes setting up the layout easier + ImGui::Begin("Timeline", NULL); + + if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) { + State->FocusedWindow = focus_timeline; + } + + // if (State->TimelineMode == timeline_mode_graph) + // ImGui_GraphInfo(File, State, Memory, UI, io, SortedPropertyStart, SortedKeyframeArray); + + real32 FontHeight = ImGui::GetFontSize(); + + ImVec2 WindowSize = ImGui::GetWindowSize(); + + if (WindowSize.x < 50 || WindowSize.y < 50) { + ImGui::PopStyleVar(2); + ImGui::End(); + return; + } + + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + + ImVec2 WindowMinAbs = ImGui::GetWindowPos(); + ImVec2 WindowMaxAbs = WindowMinAbs + WindowSize; + + ImVec2 ButtonSize = ImVec2(FontHeight*2, FontHeight*2); + + ImVec2 TimelineBorderPadding = ImVec2(FontHeight, FontHeight); + + ImVec2 TimelineSize = ImVec2(WindowSize.x, WindowSize.y); + ImVec2 TimelineSizeWithBorder = TimelineSize - TimelineBorderPadding*2; + ImVec2 TimelineAbsolutePos = WindowMinAbs + TimelineBorderPadding; + + ImVec2 KeyframeSize = ImVec2(FontHeight, FontHeight); + + int32 FrameCount = MainComp->Frame_Count; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, + IM_COL32(255, 255, 255, 50)); + + + ImGui::BeginChild("Timeline", TimelineSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, ItemSpacing.y)); + + ImGui::PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); + draw_list->PushClipRect(TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, true); + + real32 LayerIncrement = 40; + + ImVec2 Val_Min(0, 0); + ImVec2 Val_Max(40, LayerIncrement); + + // DebugWatchVar("Selection", &State->RecentSelectionType, d_int); + + // ImVec2 *ActivePercentZoomed = (UI->TimelineMode != timeline_mode_graph) ? &UI->TimelinePercentZoomed : &UI->GraphPercentZoomed; + // ImVec2 *ActivePercentOffset = (UI->TimelineMode != timeline_mode_graph) ? &UI->TimelinePercentOffset : &UI->GraphPercentOffset; + ImVec2 *ActivePercentZoomed = &UI->TimelinePercentZoomed; + ImVec2 *ActivePercentOffset = &UI->TimelinePercentOffset; + + if (ActivePercentZoomed->x == 0) { + *ActivePercentZoomed = ImVec2(1, 1); + } + + ImVec2 ActiveZoomSize = TimelineSizeWithBorder / *ActivePercentZoomed; + ImVec2 ActiveMoveSize = TimelineSizeWithBorder * *ActivePercentOffset / *ActivePercentZoomed; + + ImVec2 TimelineZoomSize = TimelineSizeWithBorder / UI->TimelinePercentZoomed; + ImVec2 TimelineMoveSize = TimelineSizeWithBorder * UI->TimelinePercentOffset / UI->TimelinePercentZoomed; + + // DebugWatchVar("TimelineY: ", &TimelineMoveSize.y, d_float); + + ImVec2 Increment = ImVec2((real32)1 / MainComp->Frame_Count, (real32)1 / LayerIncrement); + + ImGui_Timeline_BGElements(File, State, Memory, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp, TimelineZoomSize, TimelineMoveSize); + + int16 RecursionIdx[MAX_PRECOMP_RECURSIONS] = {}; + int32 Recursions = 0; + RecursionIdx[0] = -1; + ImGui_Timeline_DrawPrecomp(File, State, Memory, UI, io, draw_list, RecursionIdx, Recursions, + Increment, TimelineAbsolutePos, TimelineMoveSize, TimelineZoomSize, + TimelineSize, TimelineSizeWithBorder, LayerIncrement, + SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray); + + if (State->TimelineMode == timeline_mode_graph) { + + if (UI->GraphMoveSize.y == 0) { + UI->GraphZoomSize = ImVec2(1, UI->TimelinePercentZoomed.y ); + UI->GraphMoveSize = ImVec2(0, -UI->TimelinePercentOffset.y ); + } + + ImVec2 ZoomDifference = (UI->TimelinePercentZoomed / UI->GraphZoomSize); + ImVec2 MoveDifference = (UI->TimelinePercentOffset + (UI->GraphMoveSize)); + // DebugWatchVar("zoomdif: ", &ZoomDifference.y, d_float); + // DebugWatchVar("movedif: ", &MoveDifference.y, d_float); + ImVec2 GraphZoomSize = TimelineSizeWithBorder / ZoomDifference; + ImVec2 GraphMoveSize = TimelineSizeWithBorder * (MoveDifference) / UI->TimelinePercentZoomed; + // DebugWatchVar("zoomsize: ", &GraphZoomSize.y, d_float); + // DebugWatchVar("movesize: ", &GraphMoveSize.y, d_float); + + draw_list->AddRectFilled(WindowMinAbs, WindowMaxAbs, + IM_COL32(50, 50, 50, 230)); + ImGui_Timeline_DrawGraph(File, State, Memory, UI, io, draw_list, + Increment, TimelineAbsolutePos, GraphMoveSize, GraphZoomSize, + TimelineSize, TimelineSizeWithBorder, LayerIncrement, + SortedCompArray, SortedLayerArray, + SortedPropertyStart, SortedKeyframeArray); + } + + ImGui_Timeline_HorizontalIncrementDraw(State, UI, draw_list, TimelineSizeWithBorder, TimelineAbsolutePos, *MainComp, TimelineZoomSize, TimelineMoveSize); + + + ImVec2 MouseDelta = io.MouseDelta / TimelineSize; + + real32 BarHandleSize = FontHeight; + real32 BarThickness = 50; + real32 BarMinZoom = 0.01; + + real32 BarH_Pos = -TimelineSizeWithBorder.x * ActivePercentOffset->x; + real32 BarH_Size = TimelineSizeWithBorder.x / (1 / ActivePercentZoomed->x); + + // I use "UI" to denote the size/position after clipping the bar so that it + // doesn't go out of bounds and the handles are always selectable at the edges. + + real32 BarH_Offset = Max(BarH_Pos, 0); + + real32 BarH_SizeUI = (BarH_Size + BarH_Pos > TimelineSizeWithBorder.x) ? + TimelineSizeWithBorder.x - BarH_Pos : + BarH_Size + (BarH_Pos - BarH_Offset); + + if (BarH_Offset == 0 && BarH_SizeUI > TimelineSizeWithBorder.x) + BarH_SizeUI = TimelineSizeWithBorder.x; + + BarH_SizeUI = BarH_SizeUI - BarHandleSize*2; + + BarH_SizeUI = Max(BarH_SizeUI, FontHeight*4); + + BarH_Offset = Min(BarH_Offset, TimelineSize.x - BarH_SizeUI - BarHandleSize*4); + ImVec2 BarH_PosUI = TimelineAbsolutePos + ImVec2(BarH_Offset, TimelineSize.y - BarThickness); + bool32 BarHeld = false; + + ImGui::SetCursorScreenPos(BarH_PosUI); + ImGui::Button("##scrollbarleft", ImVec2(BarHandleSize, BarThickness)); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + if ((ActivePercentZoomed->x - MouseDelta.x) > BarMinZoom) { + ActivePercentZoomed->x -= MouseDelta.x; + ActivePercentOffset->x -= MouseDelta.x; + } + BarHeld = true; + } + + ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0)); + ImGui::Button("##scrollbarhori", ImVec2(BarH_SizeUI, BarThickness)); + + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + { + ActivePercentOffset->x -= MouseDelta.x; + BarHeld = true; + } + + ImGui::SetCursorScreenPos(BarH_PosUI + ImVec2(BarHandleSize, 0) + ImVec2(BarH_SizeUI, 0)); + ImGui::Button("##scrollbarright", ImVec2(BarHandleSize, BarThickness)); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + if ((ActivePercentZoomed->x + MouseDelta.x) > BarMinZoom) { + ActivePercentZoomed->x += MouseDelta.x; + } + BarHeld = true; + } + + if (BarHeld) { + ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 1); + } + + Assert(ActivePercentZoomed->x > BarMinZoom); + + real32 BarV_MaxSize = TimelineSizeWithBorder.y - BarThickness/2; + real32 BarV_Pos = -BarV_MaxSize * ActivePercentOffset->y; + real32 BarV_Size = BarV_MaxSize / (1 / ActivePercentZoomed->y); + BarV_Size = Max(BarV_Size, FontHeight*4); + + real32 BarV_Offset = Max(BarV_Pos, 0); + + real32 BarV_SizeUI = (BarV_Size + BarV_Pos > BarV_MaxSize) ? + BarV_MaxSize - BarV_Pos : + BarV_Size + (BarV_Pos - BarV_Offset); + + if (BarV_Offset == 0 && BarV_SizeUI > BarV_MaxSize) + BarV_SizeUI = BarV_MaxSize; + + BarV_SizeUI = BarV_SizeUI - BarHandleSize*2; + + BarV_SizeUI = Max(BarV_SizeUI, FontHeight*4); + + BarV_Offset = Min(BarV_Offset, BarV_MaxSize - BarV_SizeUI - BarHandleSize*4); + ImVec2 BarV_PosUI = TimelineAbsolutePos + ImVec2(TimelineSize.x - BarThickness, BarV_Offset); + BarHeld = false; + + ImGui::SetCursorScreenPos(BarV_PosUI); + ImGui::Button("##h-scrollbarleft", ImVec2(BarThickness, BarHandleSize)); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + ActivePercentZoomed->y -= MouseDelta.y; + ActivePercentOffset->y -= MouseDelta.y; + BarHeld = true; + } + + ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize)); + ImGui::Button("##h-scrollbar", ImVec2(BarThickness, BarV_SizeUI)); + + if (ImGui::IsItemHovered() && io.MouseWheel) + { + ActivePercentOffset->y -= io.MouseWheel/10; + } + + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1)) + { + ActivePercentOffset->y -= MouseDelta.y; + BarHeld = true; + } + + ImGui::SetCursorScreenPos(BarV_PosUI + ImVec2(0, BarHandleSize) + ImVec2(0, BarV_SizeUI)); + ImGui::Button("##h-scrollbarright", ImVec2(BarThickness, BarHandleSize)); + + if ((ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1))) + { + ActivePercentZoomed->y += MouseDelta.y; + BarHeld = true; + } + + ActivePercentZoomed->y = Max(ActivePercentZoomed->y, 0.01); + + if (BarHeld) { + ImGui_WarpMouse(State, io.MousePos, TimelineAbsolutePos, TimelineAbsolutePos + TimelineSizeWithBorder, 2); + } + + ImGui::SetCursorScreenPos(TimelineAbsolutePos); + ImGui::InvisibleButton("TimelineMoving", TimelineSizeWithBorder, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + // ImGui::Button("TimelineMoving", TimelineSizeWithBorder); + bool32 IsHovered = ImGui::IsItemHovered(); + bool32 IsItemActive = ImGui::IsItemActive(); + bool32 IsItemActivated = ImGui::IsItemActivated(); + bool32 IsItemDeactivated = ImGui::IsItemDeactivated(); + bool32 LeftClick = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool32 RightClick = ImGui::IsMouseDown(ImGuiMouseButton_Right); + + if (IsHovered && io.MouseWheel) { + real32 Increment = 0.1; + bool32 Direction = (io.MouseWheel > 0) ? 1 : -1; + ImVec2 Offset = (io.MousePos - (TimelineAbsolutePos + ActiveMoveSize)) / ActiveZoomSize; + if (io.KeyShift) { + ActivePercentOffset->y += Increment*Direction; + } else if (io.KeyCtrl) { + ActivePercentOffset->x += Increment*Direction*0.3; + } else { + if (Direction == 1) { + *ActivePercentZoomed = *ActivePercentZoomed - (*ActivePercentZoomed * Increment); + *ActivePercentOffset = *ActivePercentOffset - ((*ActivePercentOffset * Increment) + Offset*Increment); + } else { + *ActivePercentOffset = ((*ActivePercentOffset + Offset*Increment) / (1.0f - Increment)); + *ActivePercentZoomed = (*ActivePercentZoomed / (1.0f - Increment)); + } + } + } + + if (LeftClick) { + if (IsItemActivated) { + if (!io.KeyShift && State->TimelineMode == timeline_mode_default) Layer_DeselectAll(File, State, Memory); + if (State->Interact_Active == interact_type_keyframe_move || + State->Interact_Active == interact_type_keyframe_rotate || + State->Interact_Active == interact_type_keyframe_scale) { + Keyframe_Commit(File, State, Memory, SortedCompArray, SortedLayerArray, SortedPropertyStart, SortedKeyframeArray); + } + State->BoxSelect = true; + } + if (IsItemActive) { + Assert(State->BoxSelect); + draw_list->AddRectFilled(io.MouseClickedPos[0], io.MousePos, + IM_COL32(0, 0, 200, 50)); + } + } else { + State->Warp_X = 0; + State->Warp_Y = 0; + } + + if (IsItemDeactivated) { + State->BoxSelect = false; + } + + draw_list->PopClipRect(); + ImGui::PopClipRect(); + + ImGui::PopStyleVar(); + + + ImGui::EndChild(); + + ImGui::PopStyleVar(2); + + ImGui::End(); +} diff --git a/src/include/debug.h b/src/include/debug.h new file mode 100644 index 0000000..6321679 --- /dev/null +++ b/src/include/debug.h @@ -0,0 +1,99 @@ +#if DEBUG + +static int32 *debugnull = NULL; +#define Assert(Expression) if(!(Expression)) {*debugnull = 21;} + +enum valtype { + d_float, + d_uint, + d_int +}; + +union debugval { + real32 f; + uint32 u; + int32 i; +}; + +// things that get cleared every frame with the UI +struct debug_temp +{ + valtype DebugPropertyType[16]; + debugval Val[16]; + char *String[16]; + uint32 WatchedProperties; +}; + +struct project_debug +{ + debug_temp Temp; + bool32 ToggleWindow = 1; + bool32 ReloadUI = true; + bool32 NoThreading = 0; + bool32 DisableAlpha = 0; + uint64 PixelCountTransparent; + uint64 PixelCountRendered; + uint64 PixelCountChecked; + // NOTE(fox): Pixel count isn't thread safe; don't use with multithreading! + uint64 LayerCycleCount[64]; + uint32 UndoState = 0; + uint64 ScratchSize[6]; + uint32 ScratchState = 0; +}; + +static project_debug Debug; + +static void +DebugWatchVar(char *Name, void *Address, valtype Type) { + uint32 i = Debug.Temp.WatchedProperties; + Debug.Temp.String[i] = Name; + if (Type == d_float) + Debug.Temp.Val[i].f = *(real32 *)Address; + if (Type == d_uint) + Debug.Temp.Val[i].u = *(uint32 *)Address; + if (Type == d_int) + Debug.Temp.Val[i].i = *(int32 *)Address; + Debug.Temp.DebugPropertyType[i] = Type; + Debug.Temp.WatchedProperties++; +} + +#else + +#define Assert(Expression) + +enum valtype { +}; + +union debugval { +}; + +struct debug_temp +{ +}; + +struct project_debug +{ +}; + +static void +DebugWatchVar(char *Name, void *Address, valtype Type) { +} + +static void +DebugPrintMemoryUsage(memory Memory) { +} +#endif + +#ifdef PERF + +struct perf_stats +{ + uint64 PixelCountTransparent; + uint64 PixelCountRendered; + uint64 PixelCountChecked; +}; + +static uint64 Test; + +#endif + diff --git a/src/include/defines.h b/src/include/defines.h new file mode 100644 index 0000000..8216f1c --- /dev/null +++ b/src/include/defines.h @@ -0,0 +1,52 @@ +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; + +typedef int64_t int64; +typedef int32 bool32; + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; + +typedef float real32; +typedef double real64; + +typedef uint64 ptrsize; // is there a compiler variable for 32 vs 64 bit like this? + +#define NORMALIZED_COL_MIN V4(0.0f, 0.0f, 0.0f, 0.0f) +#define NORMALIZED_COL_MAX V4(1.0f, 1.0f, 1.0f, 1.0f) +#define NORMALIZED_REAL_MIN { 0.0f } +#define NORMALIZED_REAL_MAX { 1.0f } + +#define TESS_TOL 1.5f // Level of tesselation for bezier calculations; ImGui's default value + +// All of these MIN/MAX values are arbitrarily chosen; they can probably be +// increased if the user requires it. + +#define PROPERTY_REAL_MAX 1000000 +#define PROPERTY_REAL_MIN -1000000 + +#define MAX_LAYERS 2048 +#define MAX_EFFECTS 32 +#define MAX_SOURCES 1024 +#define MAX_COMPS 1024 +#define MAX_PRECOMP_RECURSIONS 4 +#define MAX_MASKS 8 +#define MAX_PROPERTIES_PER_EFFECT 80 // Kinda high since we want to support 8 xy points of Curves data across 5 channels. +#define MAX_KEYFRAME_BLOCKS 64 +#define MAX_KEYFRAMES_PER_BLOCK 32 // max keyframes on a single channel is 2048 + +#define MAX_SELECTED_PROPERTIES 16 + +#define AmountOf(Array) sizeof((Array)) / sizeof((Array)[1]) + +#if ARM +#define GetCPUTime() 0 +#else +#define GetCPUTime() __rdtsc() +#endif + +static real32 Tau = 0.9; // tension + diff --git a/src/include/ffmpeg_backend.h b/src/include/ffmpeg_backend.h new file mode 100644 index 0000000..717a33d --- /dev/null +++ b/src/include/ffmpeg_backend.h @@ -0,0 +1,37 @@ +// NOTE(fox): Even though each layer has its own completely isolated AV +// instance, it appears two layers with the same file still share something. +// When the layers aren't at the same position in time, the playhead of one +// layer gets misaligned every few frames and causes a manual seek back to the +// position. Different files don't exhibit this behavior. + +struct av_stream_info { + uint32 Index; + AVCodecParameters *CodecParameters; // Used to supply info about the decoder. + const AVCodec* Codec; + AVStream *Stream; // Which stream, or channel, the video is in. Holds FPS info and is used to verify that the decoded packet belongs to this stream. + AVCodecContext *CodecContext; +}; + + +struct av_info { + uint8 Occupied; + + uint16 Block_Source_Index; + + int LastFrameRendered; // Convenient to know this to only seek when we actually need to. + uint64 PreviousPTS; // PTS value of the previous frame, used to check timings in debug. + + AVFormatContext *FileFormatContext; // Umbrella for everything else, seems to contain the playhead state + + av_stream_info Video; + av_stream_info Audio; + + AVPacket *Packet; + AVFrame *Frame; + + uint32 FrameCount; + uint64 PTSDuration; // likely not always 100% accurate + + SwsContext *RGBContext; +}; + diff --git a/src/include/functions.h b/src/include/functions.h new file mode 100644 index 0000000..23d741e --- /dev/null +++ b/src/include/functions.h @@ -0,0 +1,90 @@ + +// Memory + +static void Memory_Copy(uint8 *Address_Write, uint8 *Address_Read, uint64 Size); +static void Arbitrary_Zero(uint8 *Address_Write, uint64 Size); +static void Arbitrary_SwapData(memory *Memory, uint8 *Address_0, uint8 *Address_1, uint64 Size); +static void Arbitrary_ShiftData(uint8 *Address_Start, uint8 *Address_End, uint64 ShiftAmount, int32 Direction); + + +// Rudimentary guess-until-correct solver for bezier curves, used to evaluate +// the keyframe graph. Similar to the Catmull-Rom solver in CurvesSolver(). + +static real32 Bezier_SolveYForX(v2 Point_P0, v2 Point_P1, v2 Point_P2, v2 Point_P3, real32 TargetX); +static bezier_point * Bezier_LookupAddress(memory *Memory, property_channel *Property, uint16 Index, bool32 AssertExists = 1); +static void Bezier_Interact_Evaluate(project_state *State, bezier_point *PointAddress, v2 *Pos, real32 GraphZoomHeight = 1, real32 Y_Increment = 1); +// NOTE(fox): GraphZoomHeight and Y_Increment don't have to be specified if the Y value isn't needed, i.e. in Property_SortAll(). +static void Bezier_Add(memory *Memory, memory_table_list TableName, property_channel *Property, bezier_point PointData, uint16 *ArrayLocation); + + +static void ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_file Sorted); +static void ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray); +static void ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, GLuint textureID, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 *SortedKeyframeArray); +static void ImGui_Timeline(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray); + +static void ImGui_File(project_data *File, project_state *State, memory *Memory, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray); +static void ImGui_EffectsPanel(project_data *File, project_state *State, memory *Memory, ui *UI, ImGuiIO io); +static void ImGui_ColorPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io); + +static void ImGui_Menu(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io); +static void ImGui_Popups(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io); +static void ImGui_KeybindUI(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io); + +#if DEBUG +static void ImGui_DebugMemoryViewer(memory *Memory, project_state *State); +static void ImGui_DebugRenderQueue(project_state *State); +static void ImGui_DebugUndoTree(memory *Memory, project_state *State); +#endif +#if SD +static void ImGui_SD_Thumbnail(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 *SourceArray, uint16 SourceCount); +static void ImGui_SD_Prompt(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray); +#endif + +// Widgets not involving drawing UI. + +static v2 ImGui_ScreenPointToCompUV(ImVec2 ViewportMin, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 MousePos); +static void ImGui_WarpMouse(project_state *State, ImVec2 MousePos, ImVec2 Min, ImVec2 Max, int Direction = 3); +static void ImGui_WarpMouseFinish(project_state *State, ImVec2 MousePos); +static ImVec2 ImGui_Brush_CalcMousePos(project_state *State, ImGuiIO &io, ImVec2 MouseDelta, int32 i, real32 DeltaDistance, real32 DeltaSlope); +static bool32 ImGui_TestBoxSelection_Point(ImVec2 Pos, ImGuiIO &io, bool32 *Test); + + +static void +Render_Main(project_data *File, project_state *State, memory *Memory, sorted_file Sorted, ui *UI, SDL_Window *window, GLuint textureID, + void *Data, void *OutputBuffer, render_type RenderType, rectangle RenderRegion); + +static void Effect_Curves_Init(block_effect *Effect, property_channel *Property); + +void AV_IsFileSupported(char *filename, bool32 *IsVideo, bool32 *HasAudio); + +static v2 T_CompUVToLayerUV(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, v2 CompUV); +static header_effect* Effect_EntryFromID(project_state *State, char *ID); + +void Effect_Curves_Sort(memory *Memory, block_effect *Effect, uint16 *SortedPointStart, uint16 Which); +inline v2 Effect_V2(memory *Memory, block_effect *Effect, int Offset); + +static void Interact_Transform_Begin(project_data *File, memory *Memory, project_state *State, ImVec2 OGPos, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray); + +static v2 Transform_ScreenSpaceToLocal(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, ImVec2 CompPos, ImVec2 CompZoom, ImVec2 ViewportMin, ImVec2 Point); +static ImVec2 Layer_LocalToScreenSpace(project_state *State, memory *Memory, block_layer *Layer, ui *UI, uint32 PrincipalCompIndex, v2 Point); + +static v2 TransformPoint(layer_transforms T, real32 Width, real32 Height, v2 Point); + +static void Layer_GetDimensions(memory *Memory, block_layer *Layer, int *Width, int *Height); + +static void * Memory_PushScratch(memory *Memory, uint64 Size); + +static void Memory_PopScratch(memory *Memory, uint64 Size); + +void Bitmap_SwapData(uint8 *Address_0, uint8 *Address_1, uint64 Size, uint16 BytesPerPixel); +void GL_DeleteHWBuffer(gl_effect_layer *Test); + +void GL_UpdateTexture(gl_effect_layer *Test, void *Data, uint16 Width, uint16 Height, uint16 BytesPerPixel, bool32 Multisample); + +static void Transform_ApplyInteractive(interact_transform Interact, real32 *OutputX, real32 *OutputY, real32 *OutputRotation, real32 *OutputScale); + +static layer_transforms Layer_GetTransforms(block_layer *Layer); +void GL_GenAndBindTexture(GLuint *GLTexture, int Width, int Height, int BytesPerPixel, void *BufferAddress); + +static real32 Bezier_SolveYForX(v2 Point_P0, v2 Point_P1, v2 Point_P2, v2 Point_P3, real32 X); + diff --git a/src/include/gl_calls.h b/src/include/gl_calls.h new file mode 100644 index 0000000..fa0a00c --- /dev/null +++ b/src/include/gl_calls.h @@ -0,0 +1,24 @@ +struct default_gl_vertex_object { + uint32 VertexArrayObject; + uint32 VertexBufferObject; + uint32 ElementBufferObject; +}; + +struct gl_vertex_shader { + uint32 VertexArrayObject; + uint32 VertexBufferObject; +}; + +static default_gl_vertex_object DefaultVerts; +static gl_vertex_shader GL_DefaultVertexObjects; +static uint32 DefaultVertexShader; +static uint32 DefaultShaderProgram; +static uint32 MaskShaderProgram; + +float GL_DefaultVertices[] = { + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, +}; + diff --git a/src/include/imgui_internal_widgets.h b/src/include/imgui_internal_widgets.h new file mode 100644 index 0000000..52fdc74 --- /dev/null +++ b/src/include/imgui_internal_widgets.h @@ -0,0 +1,15 @@ +#pragma once + +#include <climits> + +#include <imgui.h> + +// NOTE(fox): Appending to the standard ImGui namespace so I don't have to convert all the functions to ImGui::Function() +namespace ImGui { + IMGUI_API bool SliderLevels(const char* label, const char* label2, const char* label3, void* p_data, void* p_min, void* p_max); + IMGUI_API bool TestLine(ImVec2 P1, ImVec2 P2); + IMGUI_API bool BezierInteractive(ImVec2 p0, ImVec2 p1, ImVec2 p2, ImVec2 p3); + IMGUI_API bool LineInteractive(ImVec2 p0, ImVec2 p1); + IMGUI_API ImVec2 RatioToPoint(ImVec2 a, ImVec2 b, float ratio); +} + diff --git a/src/include/imgui_ops.h b/src/include/imgui_ops.h new file mode 100644 index 0000000..6089f94 --- /dev/null +++ b/src/include/imgui_ops.h @@ -0,0 +1,106 @@ + + +ImVec2 operator+(ImVec2 A, ImVec2 B) +{ + ImVec2 Result; + + Result.x = A.x + B.x; + Result.y = A.y + B.y; + + return Result; +} + +ImVec2 operator+(ImVec2 A, int B) +{ + ImVec2 Result; + + Result.x = A.x + B; + Result.y = A.y + B; + + return Result; +} + +ImVec2 operator-(ImVec2 A, int B) +{ + ImVec2 Result; + + Result.x = A.x - B; + Result.y = A.y - B; + + return Result; +} + +ImVec2 operator-(ImVec2 A, ImVec2 B) +{ + ImVec2 Result; + + Result.x = A.x - B.x; + Result.y = A.y - B.y; + + return Result; +} + +ImVec2 operator*(ImVec2 A, real32 B) +{ + ImVec2 Result; + + Result.x = A.x * B; + Result.y = A.y * B; + + return Result; +} + +ImVec2 operator*(ImVec2 A, ImVec2 B) +{ + ImVec2 Result; + + Result.x = A.x * B.x; + Result.y = A.y * B.y; + + return Result; +} + +ImVec2 operator/(ImVec2 A, ImVec2 B) +{ + ImVec2 Result; + + Result.x = A.x / B.x; + Result.y = A.y / B.y; + + return Result; +} + +ImVec2 operator/(ImVec2 A, real32 B) +{ + ImVec2 Result; + + Result.x = A.x / B; + Result.y = A.y / B; + + return Result; +} + +ImVec2 operator/(real32 A, ImVec2 B) +{ + ImVec2 Result; + + Result.x = A / B.x; + Result.y = A / B.y; + + return Result; +} + +inline bool32 +IsRectTouching(ImVec2 Min1, ImVec2 Max1, ImVec2 Min2, ImVec2 Max2) +{ + bool32 Result = 0; + if ((Max1.x > Min2.x && Min1.x < Min2.x) && + (Max1.y > Min2.y && Min1.y < Min2.y)) + Result = 1; + // if ( + // Result = 1; + // if (Min1.x > Min2.x) + + return(Result); +} + diff --git a/src/include/keybinds.h b/src/include/keybinds.h new file mode 100644 index 0000000..3fe5f64 --- /dev/null +++ b/src/include/keybinds.h @@ -0,0 +1,205 @@ +enum key_mode { + key_mode_all, + key_mode_viewport, + key_mode_timeline, + key_mode_graph, + key_mode_brush, + key_mode_count, +}; + +enum key_mods { + Mod_None, + Mod_Ctrl, + Mod_Alt, + Mod_Shift, + Mod_CtrlShift +}; + +struct shortcut_entry { + ImGuiKey_ Key; + key_mods Mods; + key_mode Mode; + char *Name; +}; + +static shortcut_entry ShortcutArray[] { + { ImGuiKey_None, Mod_None, key_mode_all, "Many actions/modes are escapable with the Esc key." }, + { ImGuiKey_None, Mod_None, key_mode_all, "Undo isn't fully implemented yet; beware crashes." }, + { ImGuiKey_Q, Mod_None, key_mode_all, "Quit (instantly!)" }, + { ImGuiKey_W, Mod_None, key_mode_all, "Step back one frame" }, + { ImGuiKey_E, Mod_None, key_mode_all, "Step forward one frame" }, + { ImGuiKey_V, Mod_None, key_mode_all, "Move tool" }, + { ImGuiKey_B, Mod_None, key_mode_all, "Brush tool" }, + { ImGuiKey_Space, Mod_None, key_mode_all, "Play scene" }, + { ImGuiKey_Delete, Mod_None, key_mode_all, "Delete selection (WIP)" }, + { ImGuiKey_S, Mod_Ctrl, key_mode_all, "Save" }, + { ImGuiKey_S, Mod_CtrlShift, key_mode_all, "Save as" }, + { ImGuiKey_C, Mod_Ctrl, key_mode_all, "Copy" }, + { ImGuiKey_P, Mod_Ctrl, key_mode_all, "Paste" }, + { ImGuiKey_Z, Mod_Ctrl, key_mode_all, "Undo" }, + { ImGuiKey_Z, Mod_Ctrl, key_mode_all, "Redo" }, + { ImGuiKey_Space, Mod_Shift, key_mode_all, "Focus effects search" }, + { ImGuiKey_Slash, Mod_Shift, key_mode_all, "Open help" }, + + { ImGuiKey_None, Mod_None, key_mode_viewport, "Hold right click to pan." }, + { ImGuiKey_None, Mod_None, key_mode_viewport, "Hold Z and drag left click to zoom." }, + { ImGuiKey_None, Mod_None, key_mode_viewport, "Press Enter or ctrl+click to commit a transform." }, + { ImGuiKey_T, Mod_None, key_mode_viewport, "Transform selected layers" }, + + { ImGuiKey_Tab, Mod_None, key_mode_timeline, "Switch between timeline and graph" }, + { ImGuiKey_2, Mod_None, key_mode_timeline, "Toggle precomp view" }, + { ImGuiKey_G, Mod_None, key_mode_timeline, "Toggle position keyframes" }, + { ImGuiKey_A, Mod_None, key_mode_timeline, "Toggle anchor point keyframes" }, + { ImGuiKey_R, Mod_None, key_mode_timeline, "Toggle roation keyframes" }, + { ImGuiKey_S, Mod_None, key_mode_timeline, "Toggle scale keyframes" }, + { ImGuiKey_T, Mod_None, key_mode_timeline, "Toggle time remapping keyframes" }, + { ImGuiKey_T, Mod_Shift, key_mode_timeline, "Toggle opacity keyframes" }, + { ImGuiKey_U, Mod_None, key_mode_timeline, "Toggle all active channels" }, + { ImGuiKey_N, Mod_None, key_mode_timeline, "Mark frame start" }, + { ImGuiKey_N, Mod_Shift, key_mode_timeline, "Mark frame end" }, + + { ImGuiKey_G, Mod_None, key_mode_graph, "Enter keyframe moving mode" }, + { ImGuiKey_X, Mod_None, key_mode_graph, "Constrain to X axis" }, + { ImGuiKey_Y, Mod_None, key_mode_graph, "Constrain to Y axis" }, + + { ImGuiKey_None, Mod_None, key_mode_brush, "Hold alt and drag to adjust size/hardness." }, + { ImGuiKey_X, Mod_None, key_mode_brush, "Swap FG and BG colors" }, +}; + + +struct key_entry { + ImGuiKey_ KeyIndex; + char *Name; + char *ShiftName; + uint32 Sector; + ImVec2 Offset; + real32 WidthRatio; +}; + +// dumb typing exercise +static key_entry KeyEntries[] { + { ImGuiKey_Tab, "Tab", "\0", 0, ImVec2(0, 1), 1.5f }, + { ImGuiKey_LeftArrow, "<-", "\0", 2, ImVec2(0, 4), 1.0f }, + { ImGuiKey_RightArrow, "->", "", 2, ImVec2(2, 4), 1.0f }, + { ImGuiKey_UpArrow, "/\\", "", 2, ImVec2(1, 3), 1.0f }, + { ImGuiKey_DownArrow, "\\/", "", 2, ImVec2(1, 4), 1.0f }, + { ImGuiKey_PageUp, "Up", "", 2, ImVec2(2, 0), 1.0f }, + { ImGuiKey_PageDown, "Dn", "", 2, ImVec2(2, 1), 1.0f }, + { ImGuiKey_Home, "Home", "", 2, ImVec2(1, 0), 1.0f }, + { ImGuiKey_End, "End", "", 2, ImVec2(1, 1), 1.0f }, + { ImGuiKey_Insert, "Insert", "", 2, ImVec2(0, 0), 1.0f }, + { ImGuiKey_Delete, "Delete", "", 2, ImVec2(0, 1), 1.0f }, + { ImGuiKey_Backspace, "Backspace", "", 0, ImVec2(13, 0), 2.0f }, + { ImGuiKey_Space, "Space", "", 0, ImVec2(3.75, 4), 6.5f }, + { ImGuiKey_Enter, "Enter", "", 0, ImVec2(12, 2), 2.25f}, + { ImGuiKey_Escape, "Esc", "", 1, ImVec2(0, 0), 1.0f }, + { ImGuiKey_LeftCtrl, "Ctrl", "", 0, ImVec2(0, 4), 1.25f }, + { ImGuiKey_LeftShift, "Shift", "", 0, ImVec2(0, 3), 2.5f }, + { ImGuiKey_LeftAlt, "Alt", "", 0, ImVec2(1.25, 4), 1.25 }, + { ImGuiKey_LeftSuper, "Sp", "", 0, ImVec2(2.5, 4), 1.25f }, + { ImGuiKey_RightCtrl, "Ctrl", "", 0, ImVec2(13.75, 4), 1.25f }, + { ImGuiKey_RightShift, "Shift", "", 0, ImVec2(11, 3), 2.5f }, + { ImGuiKey_RightAlt, "Alt", "", 0, ImVec2(11.25, 4), 1.25 }, + { ImGuiKey_RightSuper, "Sp", "", 0, ImVec2(10.25, 4), 1.0f }, + { ImGuiKey_Menu, "Menu", "", 0, ImVec2(12.5, 4), 1.25 }, + { ImGuiKey_0, "0", ")", 0, ImVec2(10, 0), 1.0f }, + { ImGuiKey_1, "1", "!", 0, ImVec2(1, 0), 1.0f }, + { ImGuiKey_2, "2", "@", 0, ImVec2(2, 0), 1.0f }, + { ImGuiKey_3, "3", "$", 0, ImVec2(3, 0), 1.0f }, + { ImGuiKey_4, "4", "%", 0, ImVec2(4, 0), 1.0f }, + { ImGuiKey_5, "5", "%", 0, ImVec2(5, 0), 1.0f }, + { ImGuiKey_6, "6", "^", 0, ImVec2(6, 0), 1.0f }, + { ImGuiKey_7, "7", "&", 0, ImVec2(7, 0), 1.0f }, + { ImGuiKey_8, "8", "*", 0, ImVec2(8, 0), 1.0f }, + { ImGuiKey_9, "9", "(", 0, ImVec2(9, 0), 1.0f }, + { ImGuiKey_A, "a", "A", 0, ImVec2(1, 2), 1.0f }, + { ImGuiKey_B, "b", "B", 0, ImVec2(5, 3), 1.0f }, + { ImGuiKey_C, "c", "C", 0, ImVec2(3, 3), 1.0f }, + { ImGuiKey_D, "d", "D", 0, ImVec2(3, 2), 1.0f }, + { ImGuiKey_E, "e", "E", 0, ImVec2(3, 1), 1.0f }, + { ImGuiKey_F, "f", "F", 0, ImVec2(4, 2), 1.0f }, + { ImGuiKey_G, "g", "G", 0, ImVec2(5, 2), 1.0f }, + { ImGuiKey_H, "h", "H", 0, ImVec2(6, 2), 1.0f }, + { ImGuiKey_I, "i", "I", 0, ImVec2(8, 1), 1.0f }, + { ImGuiKey_J, "j", "J", 0, ImVec2(7, 2), 1.0f }, + { ImGuiKey_K, "k", "K", 0, ImVec2(8, 2), 1.0f }, + { ImGuiKey_L, "l", "L", 0, ImVec2(9, 2), 1.0f }, + { ImGuiKey_M, "m", "M", 0, ImVec2(7, 3), 1.0f }, + { ImGuiKey_N, "n", "N", 0, ImVec2(6, 3), 1.0f }, + { ImGuiKey_O, "o", "O", 0, ImVec2(9, 1), 1.0f }, + { ImGuiKey_P, "p", "P", 0, ImVec2(10, 1), 1.0f }, + { ImGuiKey_Q, "q", "Q", 0, ImVec2(1, 1), 1.0f }, + { ImGuiKey_R, "r", "R", 0, ImVec2(4, 1), 1.0f }, + { ImGuiKey_S, "s", "S", 0, ImVec2(2, 2), 1.0f }, + { ImGuiKey_T, "t", "T", 0, ImVec2(5, 1), 1.0f }, + { ImGuiKey_U, "u", "U", 0, ImVec2(7, 1), 1.0f }, + { ImGuiKey_V, "v", "V", 0, ImVec2(4, 3), 1.0f }, + { ImGuiKey_W, "w", "W", 0, ImVec2(2, 1), 1.0f }, + { ImGuiKey_X, "x", "X", 0, ImVec2(2, 3), 1.0f }, + { ImGuiKey_Y, "y", "Y", 0, ImVec2(6, 1), 1.0f }, + { ImGuiKey_Z, "z", "Z", 0, ImVec2(1, 3), 1.0f }, + { ImGuiKey_F1, "F1", "", 1, ImVec2(2, 0), 1.0f }, + { ImGuiKey_F2, "F2", "", 1, ImVec2(3, 0), 1.0f }, + { ImGuiKey_F3, "F3", "", 1, ImVec2(4, 0), 1.0f }, + { ImGuiKey_F4, "F4", "", 1, ImVec2(5, 0), 1.0f }, + { ImGuiKey_F5, "F5", "", 1, ImVec2(6.5, 0), 1.0f }, + { ImGuiKey_F6, "F6", "", 1, ImVec2(7.5, 0), 1.0f }, + { ImGuiKey_F7, "F7", "", 1, ImVec2(8.5, 0), 1.0f }, + { ImGuiKey_F8, "F8", "", 1, ImVec2(9.5, 0), 1.0f }, + { ImGuiKey_F9, "F9", "", 1, ImVec2(11, 0), 1.0f }, + { ImGuiKey_F10, "F10","", 1, ImVec2(12, 0), 1.0f }, + { ImGuiKey_F11, "F11","", 1, ImVec2(13, 0), 1.0f }, + { ImGuiKey_F12, "F12","", 1, ImVec2(14, 0), 1.0f }, + { ImGuiKey_Apostrophe, "'", "\"", 0, ImVec2(11, 2), 1.0f }, + { ImGuiKey_Comma, ",", "<", 0, ImVec2(8, 3), 1.0f }, + { ImGuiKey_Minus, "-", "_", 0, ImVec2(11, 0), 1.0f}, + { ImGuiKey_Period, ".", ">", 0, ImVec2(9, 3), 1.0f }, + { ImGuiKey_Slash, "/", "?", 0, ImVec2(10, 3), 1.0f }, + { ImGuiKey_Semicolon, ";", ":", 0, ImVec2(10, 2), 1.0f }, + { ImGuiKey_Equal, "=", "+", 0, ImVec2(12, 0), 1.0f }, + { ImGuiKey_LeftBracket, "[", "{", 0, ImVec2(11, 1), 1.0f }, + { ImGuiKey_Backslash, "\\","|", 0, ImVec2(13, 1), 1.5f }, + { ImGuiKey_RightBracket, "]", "}", 0, ImVec2(12, 1), 1.0f }, + { ImGuiKey_GraveAccent, "`", "~", 0, ImVec2(0, 0), 1.0f }, + { ImGuiKey_CapsLock, "Caps", "", 0, ImVec2(0, 2), 1.75f }, + { ImGuiKey_ScrollLock, "\0", "", 0, ImVec2(0, 0), 1.0f }, // unused + { ImGuiKey_NumLock, "\0", "", 0, ImVec2(0, 0), 1.0f }, // + { ImGuiKey_PrintScreen, "\0", "", 0, ImVec2(0, 0), 1.0f }, // + { ImGuiKey_Pause, "\0", "", 0, ImVec2(0, 0), 1.0f }, // + { ImGuiKey_Keypad0, "0", "", 3, ImVec2(0, 4), 2.0f }, + { ImGuiKey_Keypad1, "1", "", 3, ImVec2(0, 3), 1.0f }, + { ImGuiKey_Keypad2, "2", "", 3, ImVec2(1, 3), 1.0f }, + { ImGuiKey_Keypad3, "3", "", 3, ImVec2(2, 3), 1.0f }, + { ImGuiKey_Keypad4, "4", "", 3, ImVec2(0, 2), 1.0f }, + { ImGuiKey_Keypad5, "5", "", 3, ImVec2(1, 2), 1.0f }, + { ImGuiKey_Keypad6, "6", "", 3, ImVec2(2, 2), 1.0f }, + { ImGuiKey_Keypad7, "7", "", 3, ImVec2(0, 1), 1.0f }, + { ImGuiKey_Keypad8, "8", "", 3, ImVec2(1, 1), 1.0f }, + { ImGuiKey_Keypad9, "9", "", 3, ImVec2(2, 1), 1.0f }, + { ImGuiKey_KeypadDecimal, ".", "", 3, ImVec2(2, 4), 1.0f }, + { ImGuiKey_KeypadDivide, "/", "", 3, ImVec2(1, 0), 1.0f }, + { ImGuiKey_KeypadMultiply, "*", "", 3, ImVec2(2, 0), 1.0f }, + { ImGuiKey_KeypadSubtract, "-", "", 3, ImVec2(3, 0), 1.0f }, + { ImGuiKey_KeypadAdd, "+", "", 3, ImVec2(3, 1), -2.0f }, // long keys! + { ImGuiKey_KeypadEnter, "Ent", "", 3, ImVec2(3, 3), -2.0f }, + { ImGuiKey_KeypadEqual, "", "", 3, ImVec2(0, 0), 1.0f } // unused +}; + +static char *KeyModeTitles[] = { + "All", + "Viewport", + "Timeline", + "Graph", + "Brush" +}; + +static char *KeyModText[] = { + "ERROR", + "Ctrl", + "Alt", + "Shift", + "Ctrl + Shift" +}; + +static ImVec2 SectorOffset[4] = { ImVec2(0, 1.5), ImVec2(0, 0) , ImVec2(15.5, 1.5), ImVec2(19, 1.5) }; + diff --git a/src/include/layer.h b/src/include/layer.h new file mode 100644 index 0000000..85d980d --- /dev/null +++ b/src/include/layer.h @@ -0,0 +1,50 @@ +static block_layer * +Layer_Init(project_data *File, memory *Memory); + +static void +Layer_Delete(project_data *File, project_state *State, memory *Memory, uint32 Index); + +static layer_transforms +Layer_GetTransforms(block_layer *Layer); + +static int +Layer_GetTopOffset(project_data *File, memory *Memory); + +static void +Layer_UpdateMasksEffects(project_state *State, block_layer *Layer, memory *Memory, void *EffectBitmapAddress, + int Width, int Height, int BytesPerPixel); + +static void +Layer_ToggleChannel(project_data *File, memory *Memory, int32 a); + +static void +Layer_Select(memory *Memory, project_state *State, int32 i); + +static void +Layer_Select_RecurseUp(memory *Memory, project_state *State, int32 i, int16 RecursionIdx[MAX_PRECOMP_RECURSIONS], uint32 Recursions); + +static void +Layer_DeselectAll(project_data *File, project_state *State, memory *Memory); + +static bool32 +Layer_LoopChannels(project_state *State, memory *Memory, sorted_property_array **SortedProperty, uint16 **SortedKeyframe, block_layer *Layer, + property_channel **Property, block_effect **EffectOut, int *h, int *c, int *p); + +static void +Layer_ToggleAllChannels(project_state *State, memory *Memory, block_layer *Layer, + sorted_comp_array *SortedCompStart, sorted_layer_array *SortedLayerStart, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray); + +static void +Layer_Select_Traverse(uint16 PrincipalCompIndex, memory *Memory, project_state *State, int32 IndexToFind, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + int16 RecursionIdx[MAX_PRECOMP_RECURSIONS], int32 *Recursions); + +static v2 +Layer_TraverseForPoint(project_data *File, project_state *State, memory *Memory, v2 PrincipalCompUV, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray); + +static int32 +Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 PrincipalIndex); + +static void +Layer_RecursiveDeselect(memory *Memory, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 TargetIndex, uint16 PrincipalIndex); + diff --git a/src/include/main.h b/src/include/main.h new file mode 100644 index 0000000..3c7ff83 --- /dev/null +++ b/src/include/main.h @@ -0,0 +1,686 @@ +enum instruction_mode { + instruction_mode_scalar, +#if ARM + instruction_mode_neon, +#else + instruction_mode_sse, + instruction_mode_avx +#endif +}; + +static char* BlendmodeNames[] = { + "Normal", + "Multiply", + "Color Burn", + "Linear Burn", + "Add", + "Screen", + "Overlay", + "Soft Light", + "Hard Light", + "Subtract", + "Divide", + "Difference" +}; + +enum blend_mode +{ + blend_normal, + blend_multiply, + blend_colorburn, + blend_linearburn, + blend_add, + blend_screen, + blend_overlay, + blend_softlight, + blend_hardlight, + blend_subtract, + blend_divide, + blend_difference +}; + +#define STRING_SIZE (1024 - sizeof(uint8)) // TODO(fox): Paths above STRING_SIZE length aren't handled properly. +struct block_string { + uint8 Occupied; + char Char[STRING_SIZE]; +}; + +struct bitmap_cache_status +{ + uint32 Block_End; +}; + +enum cache_entry_type +{ + cache_entry_type_assert, + cache_entry_type_comp, + cache_entry_type_source +}; + +struct cache_entry +{ + uint8 IsOccupied; + uint8 IsCached; + uint64 CycleTime; + uint32 Block_StartIndex; + enum cache_entry_type Type; + uint32 TypeInfo; + uint32 TypeInfo_Sub; +}; + +enum interpolation_type +{ + interpolation_type_linear, + interpolation_type_bezier, + interpolation_type_hold +}; + +struct bezier_point { + uint8 Occupied; + v2 Pos[3]; + interpolation_type Type; + bool32 IsSelected; + uint8 PointSelect[3]; + uint8 Color; +}; + +struct block_bezier { + uint8 Occupied; + bezier_point Point[MAX_KEYFRAMES_PER_BLOCK]; +}; + +enum selection_type +{ + selection_type_none, + selection_type_layer, + selection_type_keyframe +}; + +struct clipboard_channel { + void *Name; + uint16 LayerOffset; + uint16 KeyframeCount; +}; + +struct clipboard_contents { + selection_type Type; + clipboard_channel Channel[16]; + uint16 ChannelCount; +}; + +struct layer_bitmap_state { + // Image and video + bool32 ToUpdate = 1; + + // GL state + // gl_effect_layer Test; + // gl_effect_layer TestM; + + // Video state + int32 CurrentFrame = -1; // The last frame number rendered to the bitmap. -1 gurantees a load upon layer creation. + void *AVInfo; // Internal data containing current frame info +}; + +struct render_state +{ + struct layer_bitmap_state Bitmap[MAX_LAYERS]; + cache_entry Entry[2048]; +}; + +struct render_queue_item +{ + bool32 Type; + v2 Pos; +}; + +struct render_queue +{ + uint16 Playhead; + uint16 CurrentIdx; + render_queue_item Item[512]; +}; + +enum focused_window +{ + focus_viewport, + focus_properties, + focus_timeline +}; + +struct sorted_property_array +{ + uint32 MinYIndex; + uint32 MaxYIndex; +}; + +struct sorted_comp_array +{ + uint32 LayerCount; + uint32 CurrentSortIndex; // Used intermediately in the sorting algorithm +}; + +struct sorted_layer_array +{ + uint16 Block_Layer_Index; + real32 SortedOffset; + uint16 Sorted_Effect_Index[MAX_EFFECTS]; + uint16 SortedPropertyStart; + uint16 SortedKeyframeStart; +}; + +struct sorted_file +{ + sorted_comp_array *CompArray; + sorted_layer_array *LayerArray; + sorted_property_array *PropertyStart; + uint16 *PropertyArray; + uint16 *SourceArray; // sorted by 'recency,' wip for stablediffusion stuff + uint16 TempSourceCount; + uint64 Layer_SortSize; + uint64 Property_SortSize; + uint64 Source_SortSize; +}; + +enum timeline_mode +{ + timeline_mode_default, + timeline_mode_graph +}; + +struct pen_state { + bool32 IsActive; +}; + +struct brush_state +{ + ImVec2 UIPos; // Initial position when ctrl is held + real32 Size = 256; // Maxes at 1024 for now + real32 Hardness = 0.55f; // From 1 to 100 + real32 Spacing = 1.0f; + bool32 EraseMode = 0; + GLuint GLTexture; + v2 PrevPos; + void *PaintBuffer; + void *TransientBitmap; + uint32 LayerToPaint_Index = -1; + rectangle CacheBounds = { 99999, 99999, -99999, -99999 }; +}; + +enum interact_type +{ + interact_type_none, + interact_type_timeline_scrub, + interact_type_slider_scrub, + interact_type_layer_move, + interact_type_layer_timeadjust, + interact_type_viewport_transform, + interact_type_keyframe_move, + interact_type_keyframe_scale, + interact_type_keyframe_rotate, + interact_type_brush +}; + +char *ToolName[] { + "Move", + "Crop", + "Brush", + "Pen" +}; + +enum tool { + tool_default, + tool_crop, + tool_brush, + tool_pen, + tool_count +}; + +struct interact_transform +{ + v2 Min; + v2 Max; + real32 Radians; + v2 Position; + real32 Scale = 1.0f; + ImVec2 OGPos; + uint32 TransformMode; +}; + +enum hotkey_input +{ + hotkey_none, + hotkey_newpaintlayer, + hotkey_newlayerfromsource, + hotkey_deletelayer, + hotkey_undo, + hotkey_redo, +}; + +enum property_display_type +{ + property_display_type_standard, + property_display_type_blendmode, + property_display_type_color +}; + +struct header_property +{ + char *Name; + real32 DefaultValue; + property_display_type DisplayType; + real32 MinVal; + real32 MaxVal; + bool32 AlwaysInteger; + +}; + +struct gl_effect_layer { + bool32 Initialized; + GLuint Texture; + GLuint FramebufferObject; + uint32 Color_Renderbuffer; + uint32 Stencil_Renderbuffer; +}; + + +enum effect_display_type +{ + effect_display_type_standard, + effect_display_type_levels, + effect_display_type_curves +}; + +struct block_effect +{ + uint8 Occupied; + char ID[8]; + bool32 IsToggled; + uint32 Block_Property_Index[MAX_PROPERTIES_PER_EFFECT]; + real32 ExtraData[16]; +}; + +struct header_effect +{ + char *Name; + char *ID; // Eight-letter string that's used in the actual file + void (*func)(real32 *, int, int, int, void *, uint16); + uint16 PropertyStartIndex; + uint16 Property_Count; + effect_display_type DisplayType; + bool32 UseGL; + uint32 GLShaderIndex; +}; + +enum imgui_popups +{ + popup_none, + popup_saveas, + popup_keybinds +}; + +struct project_state +{ + bool32 UpdateKeyframes = 0; + bool32 UpdateFrame = 1; // only refreshes frame; set UpdateKeyframes to update animation + bool32 DebugDisableCache = 1; + uint32 CachedFrameCount; + + uint64 HotFramePerf = 0; + + uint32 AVCount; + + render_state Render; + render_queue Queue; + + int32 Frame_Current; + tool Tool = tool_default; + // GLuint ToolIconTex[(int)tool_count]; + pen_state Pen = {}; + brush_state Brush; + +#if STABLE + int32 CurlActive = 0; + char JSONPayload[1024*1024*4]; + real32 SDPercentDone; + real32 SDTimeEstimate; + real64 SDTimer; +#endif + + header_effect Effect[128]; + header_property Property[512]; + uint16 Playhead_Effect; + uint16 Playhead_Property; + + int32 PreviewLayer = -1; + int32 PreviewSource = -1; + + hotkey_input HotkeyInput; + + void *Dump1; + void *Dump2; + + char DummyName[512]; + char DummyName2[512]; + + bool32 IsRunning = 1; + bool32 IsPlaying; + bool32 PlayAudio; + int32 FramePlayingCount; + bool32 FirstFrame = 1; + int32 AudioLayerIndex = -1; + + void *ClipboardBuffer; + uint64 ClipboardSize; + + int16 MostRecentlySelectedLayer = -1; + + // NOTE(fox): Try to use this only where you actually need it (the + // ambiguous case of copying keyframes versus layers), since this state + // transfer will get buggy if you expand it to everything. + selection_type RecentSelectionType = selection_type_none; + + interact_type Interact_Active; + int32 Interact_Modifier; + real32 Interact_Offset[12]; + void *Interact_Address; + + int32 Initializing = 3; + + int32 MsgTime; // currently in "frames" + char *Msg; + + imgui_popups ImGuiPopups; + char Filename[512]; + + ImGuiTextFilter filter; // This filter API is pretty ballin'. + bool32 RerouteEffects; // Allows shift+space hotkey to gain focus on the effects panel. + + ImDrawListSplitter Test; + ImDrawListSplitter Split_KeybindUI; + + int32 Warp_X = 0; + int32 Warp_Y = 0; + bool32 Warp_WantSetPos = false; + ImVec2 Warp_PositionToSet; + real32 Warp_PositionInitial; + int32 Warp_Direction; + + uint32 InteractTransformMode; // Whether a drag on the Shift+T UI is scale (1), rotation (2), or position (3). + + timeline_mode TimelineMode; + + bool32 BoxSelect; + + focused_window FocusedWindow; // Convenience for adding window-specific hotkeys. + bool32 SetFocus; + v2 TempZoomRatio = V2(1, 1); +}; + +// UI info that's saved to the file and is not part of the history tree +struct ui +{ + ImVec2 CompZoom; // In screen pixels, not percentage. + ImVec2 CompPos; + + // Under 1 is zoomed in! + ImVec2 TimelinePercentZoomed; + ImVec2 TimelinePercentOffset; + + ImVec2 GraphZoomSize; + ImVec2 GraphMoveSize; + + v4 Color = {1, 1, 1, 1}; + v4 AltColor = {0, 0, 0, 1}; + bool32 IsPrimary; + +#if STABLE + sd_state SD; + bool32 StableEnabled = 0; +#endif + + ImU32 LayerColors[16] = { + 0xff8b1f1f, + 0xffc25909, + 0xff57c20a, + 0xff8ee6da, + 0xffa48fb7, + 0xffd14061, + 0xff38b683, + 0xff3fdbe5, + 0xffc9c9c9, + 0xff978489, + 0xfffaf5ab, + 0xff101010, + 0xffa024ca, + 0xfffae920, + 0xff208dfa, + 0xfffa2051 + }; +}; + + +struct project_data +{ + uint8 Occupied; + uint16 Layer_Count; + uint16 Source_Count; + uint16 Comp_Count; + uint16 PrincipalCompIndex; + ui UI; +}; + +struct block_composition +{ + uint8 Occupied; + + uint16 Name_String_Index; + + uint16 Width; + uint16 Height; + uint16 BytesPerPixel; + uint16 FPS; + + uint32 Frame_Count; + int32 Frame_Start; + int32 Frame_End; +}; + +struct layer_transforms +{ + real32 x; + real32 y; + real32 ax; + real32 ay; + real32 rotation; + real32 scale; +}; + +enum source_type { + source_type_none, + source_type_principal, + source_type_principal_temp, + source_type_file +}; + +struct block_source +{ + uint8 Occupied; + + bool32 IsSelected; + + uint16 Path_String_Index; + uint16 Name_String_Index; + + // Only used for type_principal + uint16 Bitmap_Index; + + uint16 Width; + uint16 Height; + uint16 BytesPerPixel; + + // LibAV specific + real32 FPS; + bool32 HasAudio; + bool32 HasVideo; + + uint32 RelativeTimestamp; + GLuint ThumbnailTex; + + source_type Type; +}; + +struct property_channel { + uint8 Occupied; + uint16 Block_Bezier_Index[MAX_KEYFRAME_BLOCKS]; + uint16 Block_Bezier_Count; + uint16 Keyframe_Count; + + int32 Identifier; + + real32 CurrentValue; + real32 MaxVal; + real32 MinVal; + bool32 AlwaysInteger; + real32 ScrubVal; // increment when dragging on sliders, etc. + + bool32 IsToggled; +}; + +char *DefaultChannel[] = { "X Position", "Y Position", "Anchor X", "Anchor Y", + "Rotation", "Scale", "Opacity", "Frame Number" }; + +struct block_layer { + uint8 Occupied; + + bool32 IsPrecomp; + bool32 Precomp_Toggled; + uint16 Block_Source_Index; // also used for precomp + uint16 Block_String_Index; + uint16 Block_Composition_Index; + + uint16 Block_Mask_Index[MAX_MASKS]; + uint16 Block_Mask_Count; + + uint16 Block_Effect_Index[MAX_EFFECTS]; + uint16 Block_Effect_Count; + + blend_mode BlendMode; + + union + { + property_channel Property[8]; + struct + { + property_channel x; + property_channel y; + property_channel ax; + property_channel ay; + property_channel rotation; + property_channel scale; + property_channel opacity; + property_channel time; + }; + }; + + bool32 IsSelected; + bool32 IsVisible; + bool32 IsAdjustment; + + int32 Frame_Start; + int32 Frame_End; + + real32 Vertical_Offset; + real32 Vertical_Height = 1; + + uint16 ColIndex; +}; + +struct render_byte_info { + uint32 MaskPixel; + uint32 ByteOffset; + uint32 Bits; + real32 Normalized; +}; + +struct transform_info { + real32 XAxisPX; + real32 XAxisPY; + real32 YAxisPX; + real32 YAxisPY; + + real32 LayerWidth; + real32 LayerHeight; + real32 LayerBytesPerPixel; + real32 LayerPitch; + render_byte_info LayerBits; + + real32 BufferWidth; + real32 BufferHeight; + real32 BufferBytesPerPixel; + real32 BufferPitch; + render_byte_info BufferBits; + + real32 LayerOpacity; + real32 OriginX; + real32 OriginY; + blend_mode BlendMode; + + bool32 IsAdjustment; + + rectangle ClipRect; + void *SourceBuffer; +}; + +struct direct_info { + real32 BufferWidth; + real32 BufferHeight; + real32 BufferBytesPerPixel; + real32 BufferPitch; + render_byte_info BufferBits; + + real32 Opacity; + + blend_mode BlendMode; + rectangle ClipRect; + void *SourceBuffer; + + bool32 SwapActive; + bool32 OnlyBlendAlpha; +}; + +struct brush_info { + uint32 BrushLength; + rectangle LayerBounds; + uint32 LayerPitch; + uint32 BrushPitch; + int32 ExtraX; + int32 ExtraY; + render_byte_info LayerBits; + render_byte_info BrushBits; + int BytesPerPixel; + int SourceWidth; + int SourceBytesPerPixel; + void *SourceBuffer; + void *BrushBuffer; + real32 R_Brush; + real32 G_Brush; + real32 B_Brush; + real32 A_Brush; + bool32 EraseMode; + uint8 *BrushRow; +}; + +enum render_type { + render_type_main, + render_type_notransform, + render_type_notransform_swap, + render_type_brush +}; + +struct render_entry { + void *RenderData; + void *OutputBuffer; + render_type RenderType; + rectangle RenderRegion; +}; + diff --git a/src/include/memory.h b/src/include/memory.h new file mode 100644 index 0000000..dab6ed4 --- /dev/null +++ b/src/include/memory.h @@ -0,0 +1,71 @@ +enum memory_table_list { + + P_AVInfo, + P_UndoBuffer, + P_MiscCache, + + F_File, + F_Precomps, + F_Layers, + F_Sources, + F_Properties, + F_Bezier, + F_Effects, + F_Strings, + F_PrincipalBitmaps, + + B_Thumbnails, + B_ScratchSpace, + B_CacheEntries, + B_CachedBitmaps, +}; + +struct memory_table { + char *Name; + void *Address; + uint64 Size; + uint32 Block_ElementSize; +}; + +struct global_memory { + void *Address; + uint64 CurrentPosition; + uint64 Size; +}; + +enum history_action_type { + action_type_swap, + action_type_swap_bitmap, + action_type_shift +}; + +struct history_action { + memory_table_list TableName; + history_action_type Type; + uint64 Size; + uint64 ByteOffset; + uint64 ShiftAmount; // Only for type_shift + int16 Direction; // Only for type_shift +}; + +struct history_entry { + char *Name; + uint16 NumberOfActions; +}; + +struct history_entry_list { + history_entry Entry[256]; + history_action Action[1024]; + uint16 NumberOfEntries; + uint16 EntryPlayhead; +}; + +struct memory { + memory_table Slot[16]; + history_entry_list History; + uint64 ScratchPos; + uint32 EntryCount; + bool32 IsFileSaved; + bool32 PurgeCache; +}; + diff --git a/src/include/my_math.h b/src/include/my_math.h new file mode 100644 index 0000000..92d43b7 --- /dev/null +++ b/src/include/my_math.h @@ -0,0 +1,762 @@ +#define PI 3.141592653589793238 + +union v2 +{ + struct + { + real32 x, y; + }; + real32 E[2]; +}; + +union v2i +{ + struct + { + int32 x, y; + }; + int32 E[2]; +}; + +union v3 +{ + struct + { + real32 r, g, b; + }; + int32 E[3]; +}; + +union v4 +{ + struct + { + real32 r, g, b, a; + }; + real32 E[4]; +}; + +inline v2 V2(real32 x, real32 y) +{ + v2 Result; + + Result.x = x; + Result.y = y; + + return(Result); +} + +inline v2 V2(real32 x) +{ + v2 Result; + + Result.x = x; + Result.y = x; + + return(Result); +} + +inline v2 V2(ImVec2 A) +{ + v2 Result; + + Result.x = A.x; + Result.y = A.y; + + return(Result); +} + +inline ImVec2 V2(v2 A) +{ + struct ImVec2 Result; + + Result.x = A.x; + Result.y = A.y; + + return(Result); +} + +inline v2i V2i(int32 x, int32 y) +{ + v2i Result; + + Result.x = x; + Result.y = y; + + return(Result); +} + +inline v3 V3(real32 a) +{ + v3 Result; + + Result.r = a; + Result.g = a; + Result.b = a; + + return(Result); +} + +inline v4 V4(real32 r) +{ + v4 Result; + + Result.r = r; + Result.g = r; + Result.b = r; + Result.a = r; + + return(Result); +} + +inline v4 powv4(v4 Col, real32 i) +{ + v4 Result; + + Result.r = pow(Col.r, i); + Result.g = pow(Col.g, i); + Result.b = pow(Col.b, i); + Result.a = pow(Col.a, i); + + return(Result); +} + +inline v4 powv4(v4 Col, v4 i) +{ + v4 Result; + + Result.r = pow(Col.r, i.r); + Result.g = pow(Col.g, i.g); + Result.b = pow(Col.b, i.b); + Result.a = pow(Col.a, i.a); + + return(Result); +} + +inline v4 V4(v3 r, real32 a) +{ + v4 Result; + + Result.r = r.r; + Result.g = r.g; + Result.b = r.b; + Result.a = a; + + return(Result); +} + +inline v4 V4(real32 r, real32 g, real32 b, real32 a) +{ + v4 Result; + + Result.r = r; + Result.g = g; + Result.b = b; + Result.a = a; + + return(Result); +} + +inline v4 ClipV4(v4 A) +{ + v4 Result; + if (A.r < 0.0f) { + Result.r = 0.0f; + } else if (A.r > 1.0f) { + Result.r = 1.0f; + } else { + Result.r = A.r; + } + + if (A.g < 0.0f) { + Result.g = 0.0f; + } else if (A.g > 1.0f) { + Result.g = 1.0f; + } else { + Result.g = A.g; + } + + if (A.b < 0.0f) { + Result.b = 0.0f; + } else if (A.b > 1.0f) { + Result.b = 1.0f; + } else { + Result.b = A.b; + } + + if (A.a < 0.0f) { + Result.a = 0.0f; + } else if (A.a > 1.0f) { + Result.a = 1.0f; + } else { + Result.a = A.a; + } + + return(Result); +} + + +v2i operator+(v2i A, v2i B) +{ + v2i Result; + + Result.x = A.x + B.x; + Result.y = A.y + B.y; + + return(Result); +} + +v2 operator/(v2 A, real32 B) +{ + v2 Result; + + Result.x = A.x / B; + Result.y = A.y / B; + + return Result; +} + + + +v4 operator+(v4 A, v4 B) +{ + v4 Result; + + Result.r = A.r + B.r; + Result.g = A.g + B.g; + Result.b = A.b + B.b; + Result.a = A.a + B.a; + + return(Result); +} + +v4 operator-(v4 A, v4 B) +{ + v4 Result; + + Result.r = A.r - B.r; + Result.g = A.g - B.g; + Result.b = A.b - B.b; + Result.a = A.a - B.a; + + return(Result); +} + +v4 operator-(real32 A, v4 B) +{ + v4 Result; + + Result.r = A - B.r; + Result.g = A - B.g; + Result.b = A - B.b; + Result.a = A - B.a; + + return(Result); +} + +v4 operator-(v4 A, real32 B) +{ + v4 Result; + + Result.r = A.r - B; + Result.g = A.g - B; + Result.b = A.b - B; + Result.a = A.a - B; + + return(Result); +} + +v4 operator*(v4 A, v4 B) +{ + v4 Result; + + Result.r = A.r * B.r; + Result.g = A.g * B.g; + Result.b = A.b * B.b; + Result.a = A.a * B.a; + + return(Result); +} + +v4 operator/(v4 A, v4 B) +{ + v4 Result; + + Result.r = A.r / B.r; + Result.g = A.g / B.g; + Result.b = A.b / B.b; + Result.a = A.a / B.a; + + return(Result); +} + +v4 operator/(real32 A, v4 B) +{ + v4 Result; + + Result.r = A / B.r; + Result.g = A / B.g; + Result.b = A / B.b; + Result.a = A / B.a; + + return(Result); +} + +v4 operator*(v4 A, real32 B) +{ + v4 Result; + + Result.r = A.r * B; + Result.g = A.g * B; + Result.b = A.b * B; + Result.a = A.a * B; + + return(Result); +} + +v4 operator*(real32 B, v4 A) +{ + v4 Result; + + Result.r = A.r * B; + Result.g = A.g * B; + Result.b = A.b * B; + Result.a = A.a * B; + + return(Result); +} + +v2 operator-(v2 A, v2i B) +{ + v2 Result; + + Result.x = A.x - (real32)B.x; + Result.y = A.y - (real32)B.y; + + return(Result); +} + +v2 operator/(v2 A, v2 B) +{ + v2 Result; + + Result.x = A.x / B.x; + Result.y = A.y / B.y; + + return(Result); +} + +v2i operator-(v2i A, v2i B) +{ + v2i Result; + + Result.x = A.x - B.x; + Result.y = A.y - B.y; + + return(Result); +} + +v2i operator-(v2i A, int16 B) +{ + v2i Result; + + Result.x = A.x - B; + Result.y = A.y - B; + + return(Result); +} + +v2i operator+(v2i A, int16 B) +{ + v2i Result; + + Result.x = A.x + B; + Result.y = A.y + B; + + return(Result); +} + +v2 operator*(real32 A, v2 B) +{ + v2 Result; + + Result.x = A * B.x; + Result.y = A * B.y; + + return(Result); +} + +v2 operator*(v2 A, real32 B) +{ + v2 Result; + + Result.x = A.x * B; + Result.y = A.y * B; + + return(Result); +} + + +v2 operator-(v2 A, v2 B) +{ + v2 Result; + + Result.x = A.x - B.x; + Result.y = A.y - B.y; + + return(Result); +} + +v2 operator-(v2 A, real32 B) +{ + v2 Result; + + Result.x = A.x - B; + Result.y = A.y - B; + + return(Result); +} + +v2 operator-(v2 A) +{ + v2 Result; + + Result.x = -A.x; + Result.y = -A.y; + + return(Result); +} + +v2 operator+(v2 A, v2 B) +{ + v2 Result; + + Result.x = A.x + B.x; + Result.y = A.y + B.y; + + return(Result); +} + +v2 operator*(v2 A, v2 B) +{ + v2 Result; + + Result.x = A.x * B.x; + Result.y = A.y * B.y; + + return(Result); +} + +struct rectangle +{ + v2i Min; + v2i Max; +}; + +inline real32 +Min(real32 A, real32 B) +{ + return (A > B) ? B : A; +} + +inline real32 +Max(real32 A, real32 B) +{ + return (A > B) ? A : B; +} + +inline bool32 +TestRectangle(rectangle Rect, v2i Test) +{ + bool32 Result = (Test.x > Rect.Min.x && Test.x < Rect.Max.x && + Test.y > Rect.Min.y && Test.y < Rect.Max.y); + return(Result); +} + +inline bool32 +TestRectangle(ImVec2 Min, ImVec2 Max, ImVec2 Test) +{ + bool32 Result = (Test.x > Min.x && Test.x < Max.x && + Test.y > Min.y && Test.y < Max.y); + return(Result); +} + +inline rectangle +ClipRectangle(rectangle Rect, rectangle BoundRect) +{ + if(Rect.Min.x < BoundRect.Min.x) + { + Rect.Min.x = BoundRect.Min.x; + } + if(Rect.Min.y < BoundRect.Min.y) + { + Rect.Min.y = BoundRect.Min.y; + } + if(Rect.Max.x > BoundRect.Max.x) + { + Rect.Max.x = BoundRect.Max.x; + } + if(Rect.Max.y > BoundRect.Max.y) + { + Rect.Max.y = BoundRect.Max.y; + } + return(Rect); +} + +inline rectangle +VerifyMinMax(rectangle Rect) +{ + if(Rect.Min.x > Rect.Max.x) + { + int16 Temp = Rect.Max.x; + Rect.Max.x = Rect.Min.x; + Rect.Min.x = Temp; + } + if(Rect.Min.y > Rect.Max.y) + { + int16 Temp = Rect.Max.y; + Rect.Max.y = Rect.Min.y; + Rect.Min.y = Temp; + } + return(Rect); +} + +inline int32 +RoundReal32ToInt32(real32 Real32) +{ + int32 Result = (int32)(Real32 + 0.5f); + return(Result); +} + +inline uint32 +RoundReal32ToUint32(real32 Real32) +{ + uint32 Result = (uint32)(Real32 + 0.5f); + return(Result); +} + +inline uint32 +ColToUint32(real32 B) +{ + uint32 Result = ((RoundReal32ToUint32(1 * 255.0f) << 24) | + (RoundReal32ToUint32(B * 255.0f) << 16) | + (RoundReal32ToUint32(B * 255.0f) << 8) | + (RoundReal32ToUint32(B * 255.0f) << 0)); + return Result; +} + +inline uint32 +ColToUint32(v4 Col) +{ + uint32 Result = ((RoundReal32ToUint32(Col.a * 255.0f) << 24) | + (RoundReal32ToUint32(Col.r * 255.0f) << 16) | + (RoundReal32ToUint32(Col.g * 255.0f) << 8) | + (RoundReal32ToUint32(Col.b * 255.0f) << 0)); + return Result; +} + +inline real32 +Square(real32 A) +{ + real32 Result = A * A; + return Result; +} + +inline real32 +Inner(v2 A, v2 B) +{ + real32 Result = A.x * B.x + A.y * B.y; + + return Result; +} + +inline real32 +Inner(ImVec2 A, ImVec2 B) +{ + real32 Result = A.x * B.x + A.y * B.y; + + return Result; +} + +inline real32 +LengthSq(v2 A) +{ + real32 Result = Inner(A, A); + return Result; +} + +inline uint32 +Floor(uint32 A, uint32 B) +{ + if (A < B) { + A = B; + } + return A; +} + +inline uint32 +Ceil(uint32 A, uint32 B) +{ + if (A > B) { + A = B; + } + return A; +} + +inline real32 +Floor(real32 A, real32 B) +{ + if (A < B) { + A = B; + } + return A; +} + +inline real32 +Ceil(real32 A, real32 B) +{ + if (A > B) { + A = B; + } + return A; +} + +inline bool32 +TestUV(v2 UV) { + return (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f); +} + +inline real32 +Clamp(real32 A, real32 B, real32 C) +{ + if (B < A) { + B = A; + } + if (B > C) { + B = C; + } + return B; +} + +inline v4 +Clamp(real32 A, v4 B, real32 C) +{ + B.r = Clamp(A, B.r, C); + B.g = Clamp(A, B.g, C); + B.b = Clamp(A, B.b, C); + B.a = Clamp(A, B.a, C); + return B; +} + + + +inline v4 +Lerp(v4 A, real32 t, v4 B) +{ + v4 Result = (1.0f - t)*A + t*B; + + return Result; +} + +inline v4 +Uint32ToCol(uint32 LayerPixel) +{ + uint8 A2 = (LayerPixel >> 24); + uint8 R2 = (LayerPixel >> 16); + uint8 G2 = (LayerPixel >> 8); + uint8 B2 = (LayerPixel >> 0); + v4 Color = {(real32)R2 / 255.0f, + (real32)G2 / 255.0f, + (real32)B2 / 255.0f, + (real32)A2 / 255.0f}; + return Color; +} + +inline v4 +Uint32ToCol8(uint32 LayerPixel) +{ + uint8 A2 = (LayerPixel >> 24); + uint8 B2 = (LayerPixel >> 16); + uint8 G2 = (LayerPixel >> 8); + uint8 R2 = (LayerPixel >> 0); + v4 Color = {(real32)R2, + (real32)G2, + (real32)B2, + (real32)A2}; + return Color; +} + +inline v4 +Uint32ToNormalizedCol(uint32 LayerPixel) +{ + uint8 A2 = (LayerPixel >> 24); + uint8 R2 = (LayerPixel >> 16); + uint8 G2 = (LayerPixel >> 8); + uint8 B2 = (LayerPixel >> 0); + + real32 R = R2 / 255.0f; + real32 G = G2 / 255.0f; + real32 B = B2 / 255.0f; + real32 A = A2 / 255.0f; + + v4 Result = {R,G,B,A}; + + return Result; +} + +inline real32 +Uint32ToNormalizedBW(uint32 LayerPixel) +{ + uint8 R2 = (LayerPixel >> 16); + uint8 G2 = (LayerPixel >> 8); + uint8 B2 = (LayerPixel >> 0); + + real32 R = R2 / 255.0f; + real32 G = G2 / 255.0f; + real32 B = B2 / 255.0f; + + real32 Result = (R + G + B) / 3.0f; + + return Result; +} + +inline uint32 +Col8ToUint32(v4 P) +{ + uint8 A2 = P.a; + uint8 R2 = P.r; + uint8 G2 = P.g; + uint8 B2 = P.b; + uint32 Result = ((A2 << 24) | + (R2 << 16) | + (G2 << 8) | + (B2 << 0)); + return Result; +} + + +inline uint8 +ClipAdd(uint8 a, uint8 b) +{ + uint8 Result = 0; + int16 exp = (int16)a + b; + if (exp > 255) { + Result = 255; + } else if (exp < 0) { + Result = 0; + } else { + Result = a + b; + } + return Result; +} + +inline real32 Normalize(real32 A) +{ + if (A > 1) { + A = 1.0f; + } else if (A < 0.0f) { + A = 0.0f; + } + return A; +} + diff --git a/src/include/sharebuffer.h b/src/include/sharebuffer.h new file mode 100644 index 0000000..c25deb2 --- /dev/null +++ b/src/include/sharebuffer.h @@ -0,0 +1,17 @@ +#include <sys/mman.h> +#include <fcntl.h> +#include <semaphore.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#define SHAREDMEMORY_SIZE ((uint64_t)20*1024*1024) + +struct SharedMemoryInfo { + sem_t sem1; + sem_t sem2; + int16_t shared_framenumber; + char BitmapData; +}; + diff --git a/src/include/stable_diffusion.h b/src/include/stable_diffusion.h new file mode 100644 index 0000000..3335796 --- /dev/null +++ b/src/include/stable_diffusion.h @@ -0,0 +1,33 @@ + + +// curl -X POST -H 'Content-Type: application/json' -i 'http://127.0.0.1:7860/sdapi/v1/txt2img' --data '{ +// "prompt": "cute dinosaur sticker with polka dots", +// "steps": 50, +// "sampler_index": "DDIM" +// }' + +#define SD_LEN_PROMPT 256 +#define SD_LEN_ADDRESS 128 + +// struct sd_mode +// { +// sd_mode_txt2txt, +// sd_mode_txt2img +// } + +struct sd_state +{ + bool32 Mode = 1; + char Prompt[256]; + char NegPrompt[256]; + char ServerAddress[128]; + int32 Steps = 10; + int32 Width = 512; + int32 Height = 512; + int32 SamplerIndex = 0; + real32 CFG = 7; + int32 BatchSize = 1; + real32 DenoisingStrength = 0.4; + int32 Seed = -1; +}; + diff --git a/src/include/structs.h b/src/include/structs.h new file mode 100644 index 0000000..fbb97ed --- /dev/null +++ b/src/include/structs.h @@ -0,0 +1,5 @@ +struct effect; + +struct action_entry; +struct action_entries; + diff --git a/src/include/undo.h b/src/include/undo.h new file mode 100644 index 0000000..139597f --- /dev/null +++ b/src/include/undo.h @@ -0,0 +1,2 @@ + + diff --git a/src/io.cpp b/src/io.cpp new file mode 100644 index 0000000..50ffbcf --- /dev/null +++ b/src/io.cpp @@ -0,0 +1,23 @@ +static void +IO_WriteToStream(void *Address, uint64 FileSize, SDL_RWops *TestFile) +{ + uint64 Size_Written = SDL_RWwrite(TestFile, Address, 1, FileSize); + + if (Size_Written != FileSize) + Assert(0); +} + +static void +IO_ReadFromStream(void *Address, uint64 SizeToRead, SDL_RWops *File) +{ + int64 TotalBytesRead = 0; + uint8 *Address_Playhead = (uint8 *)Address; + while (TotalBytesRead < SizeToRead) { + uint64 BytesRead = SDL_RWread(File, Address, 1, (SizeToRead - TotalBytesRead)); + if (BytesRead == 0) + break; + TotalBytesRead += BytesRead; + Address_Playhead += BytesRead; + } + Assert(TotalBytesRead == SizeToRead); +} diff --git a/src/layer.cpp b/src/layer.cpp new file mode 100644 index 0000000..f1ce0f4 --- /dev/null +++ b/src/layer.cpp @@ -0,0 +1,405 @@ + +static property_channel +Property_InitFloat(real32 Val, real32 ScrubVal, real32 MinVal = PROPERTY_REAL_MIN, real32 MaxVal = PROPERTY_REAL_MAX, bool32 AlwaysInteger = 0); + +static block_layer * +Layer_Init(project_data *File, memory *Memory) +{ + if (File->Layer_Count + 1 > MAX_LAYERS) { + Assert(0); + } + block_layer *Layer = (block_layer *)Memory_Block_AllocateAddress(Memory, F_Layers); + History_Action_Block_Swap(Memory, F_Layers, Layer); + + *Layer = {}; + Layer->Occupied = 1; + + Layer->Block_String_Index = Memory_Block_AllocateNew(Memory, F_Strings); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index, 0); + sprintf(String->Char, "Layer %i", File->Layer_Count + 1); // CSbros... + History_Action_Swap(Memory, F_File, sizeof(String->Occupied), &String->Occupied); + String->Occupied = 1; + + Layer->x = Property_InitFloat(0.0f, 1.0f); + Layer->y = Property_InitFloat(0.0f, 1.0f); + Layer->ax = Property_InitFloat(0.5f, 0.005f); + Layer->ay = Property_InitFloat(0.5f, 0.005f); + Layer->scale = Property_InitFloat(1.0f, 0.005f); + Layer->rotation = Property_InitFloat(0.0f, 1.0f); + Layer->opacity = Property_InitFloat(1.0f, 0.005f, 0.0f, 1.0f); + Layer->time = Property_InitFloat(0.0f, 1.0f, 0, 100000, 1); + + Layer->IsVisible = 1; + + History_Action_Swap(Memory, F_File, sizeof(File->Layer_Count), &File->Layer_Count); + File->Layer_Count++; + + return Layer; +} + +// lots of cleanup... +static void +Layer_Delete(project_data *File, project_state *State, memory *Memory, uint32 Index) +{ + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index); + History_Action_Block_Swap(Memory, F_Layers, Layer); + Layer->Occupied = 0; + + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Layer->Block_String_Index, 0); + History_Action_Block_Swap(Memory, F_Strings, String); + String->Occupied = 0; + + for (int i = 0; i < Layer->Block_Effect_Count; i++) { + block_effect *Effect = (block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, Layer->Block_Effect_Index[i]); + header_effect *EffectHeader = Effect_EntryFromID(State, Effect->ID); + for (int h = 0; h < EffectHeader->Property_Count; h++) { + header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + h]; + property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[h]); + if (Property->Block_Bezier_Count) { + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Property->Block_Bezier_Index[i], 0); + History_Action_Block_Swap(Memory, F_Bezier, Bezier); + Bezier->Occupied = 0; + } + History_Action_Block_Swap(Memory, F_Properties, Property); + Property->Occupied = 0; + } + History_Action_Block_Swap(Memory, F_Effects, Effect); + Effect->Occupied = 0; + } + History_Action_Swap(Memory, F_File, sizeof(File->Layer_Count), &File->Layer_Count); + File->Layer_Count--; +} + +static layer_transforms +Layer_GetTransforms(block_layer *Layer) { + return { Layer->x.CurrentValue, Layer->y.CurrentValue, Layer->ax.CurrentValue, Layer->ay.CurrentValue, Layer->rotation.CurrentValue, Layer->scale.CurrentValue }; +} + +static int +Layer_GetTopOffset(project_data *File, memory *Memory) +{ + if (File->Layer_Count == 0) + return 11; + int TopOffset = 9999; + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->Block_Composition_Index == File->PrincipalCompIndex) { + TopOffset = (Layer->Vertical_Offset < TopOffset) ? Layer->Vertical_Offset : TopOffset; + } + } + return TopOffset; +} + + +static void +Layer_UpdateMasksEffects(project_state *State, block_layer *Layer, memory *Memory, void *EffectBitmapAddress, + int Width, int Height, int BytesPerPixel) +{ + uint64 Size = Width*Height*BytesPerPixel; + + // We need two of these: one with multisampling enabled and a + // non-multisampled one that we can blit to. + gl_effect_layer TestL = {}; + gl_effect_layer TestM = {}; + + GL_UpdateTexture(&TestL, EffectBitmapAddress, Width, Height, BytesPerPixel, 0); + GL_UpdateTexture(&TestM, EffectBitmapAddress, Width, Height, BytesPerPixel, 1); + + for (int i = 0; i < Layer->Block_Effect_Count; i++) + { + block_effect Effect = *(block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, Layer->Block_Effect_Index[i]); + header_effect *EffectEntry = Effect_EntryFromID(State, Effect.ID); + + if (Effect.IsToggled) { + uint64 Size = (sizeof(real32) * MAX_PROPERTIES_PER_EFFECT) + (sizeof(real32) * 10); + real32 *Data; + if (EffectEntry->DisplayType == effect_display_type_curves) { + Data = (real32 *)Memory_PushScratch(Memory, Size); + uint16 SortedPointIndex[MAX_PROPERTIES_PER_EFFECT]; + v2 *SortedPointValues = (v2 *)(Data + 5); + for (int c = 0; c < 5; c++) { + *(Data + c) = Effect.ExtraData[c]; + uint32 Shift = MAX_PROPERTIES_PER_EFFECT / 5 * c; + uint16 *SortedPointIndexPlayhead = SortedPointIndex + Shift; + v2 *SortedPointValuesPlayhead = SortedPointValues + Shift; + Effect_Curves_Sort(Memory, &Effect, SortedPointIndexPlayhead, c); + for (int a = 0; a < Effect.ExtraData[c]; a++) { + *SortedPointValuesPlayhead = Effect_V2(Memory, &Effect, SortedPointIndexPlayhead[a]); + SortedPointValuesPlayhead++; + } + } + } else { + Data = (real32 *)Memory_PushScratch(Memory, Size); + for (int c = 0; c < EffectEntry->Property_Count; c++) { + property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[c]); + Data[c] = Property->CurrentValue; + } + } + EffectEntry->func(Data, Width, Height, BytesPerPixel, EffectBitmapAddress, EffectEntry->GLShaderIndex); + Memory_PopScratch(Memory, Size); + } + } + /* + if (Layer->NumberOfMasks) { + for (int i = 0; i < Layer->NumberOfMasks; i++) { + file_mask_header *MaskHeader = (file_mask_header *)((uint8 *)Layer + sizeof(file_layer) + MaskOffset); + if (MaskHeader->IsClosed && MaskHeader->IsToggled) { + mask_point *Point = (mask_point *)((uint8 *)MaskHeader + sizeof(file_mask_header)); + Mask_TriangulateAndRasterize(TestM, TestL, Memory, MaskHeader, Point, Source->Width, Source->Height, Source->BytesPerPixel, EffectBitmapAddress); + } + } + Bitmap_StencilAlpha(SourceBitmapAddress, EffectBitmapAddress, Source->BytesPerPixel, Size); + } + + Layer->OutputBitmapLocation = EffectBitmapAddress; + */ + + GL_DeleteHWBuffer(&TestL); + GL_DeleteHWBuffer(&TestM); +} + +static void +Layer_ToggleChannel(project_data *File, memory *Memory, int32 a) +{ + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) + { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected) + Layer->Property[a].IsToggled ^= 1; + } +} + +static void +Layer_Select(memory *Memory, project_state *State, int32 i) +{ + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + Layer->IsSelected = true; + State->MostRecentlySelectedLayer = i; + State->RecentSelectionType = selection_type_layer; +} + +static void +Layer_Select_RecurseUp(memory *Memory, project_state *State, int32 i, int16 RecursionIdx[MAX_PRECOMP_RECURSIONS], uint32 Recursions) +{ + for (int a = 1; a <= Recursions; a++) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, RecursionIdx[a]); + Layer->IsSelected = 2; + } +} + +static void +Layer_DeselectAll(project_data *File, project_state *State, memory *Memory) { + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + Layer->IsSelected = false; + } + State->MostRecentlySelectedLayer = -1; +} + +// h: index of the total amount of properties and effects +// c: index of the amount of properties in a given effect +// p: prior property's keyframe count, so we can increment the sorted keyframe array properly +static bool32 +Layer_LoopChannels(project_state *State, memory *Memory, sorted_property_array **SortedProperty, uint16 **SortedKeyframe, block_layer *Layer, + property_channel **Property, block_effect **EffectOut, int *h, int *c, int *p) +{ + uint32 Amount = AmountOf(Layer->Property) + Layer->Block_Effect_Count; + // Assert(Layer->Block_Effect_Count < 2); + while (*h < Amount) { + if (*h < AmountOf(Layer->Property) && *c == 0) { + *Property = &Layer->Property[*h]; + if (*h != 0) { + *SortedProperty += 1; + *SortedKeyframe += *p; + } + *h += 1; + *p = (**Property).Keyframe_Count; + return 1; + } else { + uint16 EffectIdx = Layer->Block_Effect_Index[*h - AmountOf(Layer->Property)]; + block_effect *Effect = (block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, EffectIdx); + if (EffectOut) + *EffectOut = Effect; + header_effect *EffectHeader = Effect_EntryFromID(State, Effect->ID); + while (*c < EffectHeader->Property_Count) { + // header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + c]; + *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect->Block_Property_Index[*c]); + *SortedProperty += 1; + *SortedKeyframe += *p; + *p = (**Property).Keyframe_Count; + *c += 1; + return 1; + } + *h += 1; + *c = 0; + } + } + Assert(*h != (Amount - 1)); + return 0; +} + +static void +Layer_ToggleAllChannels(project_state *State, memory *Memory, block_layer *Layer, + sorted_comp_array *SortedCompStart, sorted_layer_array *SortedLayerStart, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray) +{ + bool32 ToggleMode = 1; + { + sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayerStart->SortedPropertyStart; + uint16 *ArrayLocation = SortedKeyframeArray + SortedLayerStart->SortedKeyframeStart; + int h = 0, c = 0, p = 0; + property_channel *Property = NULL; + block_effect *Effect = NULL; + while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) + { + if (Property->IsToggled) { + ToggleMode = 0; + break; + } + } + } + sorted_property_array *InfoLocation = SortedPropertyStart + SortedLayerStart->SortedPropertyStart; + uint16 *ArrayLocation = SortedKeyframeArray + SortedLayerStart->SortedKeyframeStart; + int h = 0, c = 0, p = 0; + property_channel *Property = NULL; + block_effect *Effect = NULL; + while (Layer_LoopChannels(State, Memory, &InfoLocation, &ArrayLocation, Layer, &Property, &Effect, &h, &c, &p)) + { + if (Property->Keyframe_Count) { + Property->IsToggled = ToggleMode; + } + } +} + +static void +Layer_Select_Traverse(uint16 PrincipalCompIndex, memory *Memory, project_state *State, int32 IndexToFind, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + int16 RecursionIdx[MAX_PRECOMP_RECURSIONS], int32 *Recursions) +{ + uint16 CompIndex = 0; + if (RecursionIdx[*Recursions] == -1) { + CompIndex = PrincipalCompIndex; + } else { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, RecursionIdx[*Recursions]); + CompIndex = Layer->Block_Source_Index; + } + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); + sorted_comp_array SortedCompStart = SortedCompArray[CompIndex]; + uint32 RecursionsCurrent = *Recursions; + for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsPrecomp && Layer->IsSelected == 2) { + *Recursions = RecursionsCurrent + 1; + RecursionIdx[*Recursions] = Index_Physical; + Layer_Select_Traverse(PrincipalCompIndex, Memory, State, IndexToFind, SortedCompArray, SortedLayerArray, RecursionIdx, Recursions); + } else if (Index_Physical == IndexToFind) { + return; + } + } +} + +static v2 +Layer_TraverseForPoint(project_data *File, project_state *State, memory *Memory, v2 PrincipalCompUV, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) +{ + int16 RecursionIdx[MAX_PRECOMP_RECURSIONS] = {}; + RecursionIdx[0] = -1; + int32 Recursions = 0; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, File->PrincipalCompIndex); + sorted_comp_array SortedCompStart = SortedCompArray[File->PrincipalCompIndex]; + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + Layer_Select_Traverse(File->PrincipalCompIndex, Memory, State, State->Brush.LayerToPaint_Index, SortedCompArray, SortedLayerArray, RecursionIdx, &Recursions); + v2 PointUV = {0, 0}; + int OuterWidth = Comp->Width, OuterHeight = Comp->Height; + int InnerWidth = 0, InnerHeight = 0; + if (Recursions == 0) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->Brush.LayerToPaint_Index); + layer_transforms T = Layer_GetTransforms(Layer); + Layer_GetDimensions(Memory, Layer, &InnerWidth, &InnerHeight); + PointUV = T_CompUVToLayerUV(T, OuterWidth, OuterHeight, InnerWidth, InnerHeight, PrincipalCompUV); + } else { + for (int i = 1; i <= Recursions; i++) { + block_layer *InnerLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, RecursionIdx[i]); + layer_transforms T = Layer_GetTransforms(InnerLayer); + Layer_GetDimensions(Memory, InnerLayer, &InnerWidth, &InnerHeight); + PointUV = T_CompUVToLayerUV(T, OuterWidth, OuterHeight, InnerWidth, InnerHeight, PrincipalCompUV); + OuterWidth = InnerWidth; + OuterHeight = InnerHeight; + } + } + return PointUV * V2(InnerWidth, InnerHeight); +} + +static int32 +Layer_TestSelection(memory *Memory, project_state *State, ui *UI, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 PrincipalIndex) +{ + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, PrincipalIndex); + sorted_comp_array SortedCompStart = SortedCompArray[PrincipalIndex]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, PrincipalIndex); + int SelectionCount = 0; + int SelectedLayerIndex = 0; + for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + layer_transforms T = Layer_GetTransforms(Layer); + v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Source->Width, Source->Height, State->TempZoomRatio); + if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && Layer->IsSelected) + { + SelectionCount++; + SelectedLayerIndex = i; + } + } + int32 LayerIndex = -1; + for (int i = SortedCompStart.LayerCount - 1; i >= 0; i--) { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + layer_transforms T = Layer_GetTransforms(Layer); + v2 UV = T_CompUVToLayerUV(T, Comp->Width, Comp->Height, Source->Width, Source->Height, State->TempZoomRatio); + if (UV.x <= 1.0f && UV.x >= 0.0f && UV.y <= 1.0f && UV.y >= 0.0f && !Layer->IsSelected) + { + if (SelectionCount == 1) { + if (i < SelectedLayerIndex) { + LayerIndex = Index_Physical; + break; + } + } else { + LayerIndex = Index_Physical; + break; + } + } + // if (Layer->IsPrecomp) { + // Layer_RecursiveDeselect(Memory, SortedCompArray, SortedLayerArray, TargetIndex, Layer->Block_Source_Index); + // } + // if (Layer->Block_Composition_Index != TargetIndex) { + // Layer->IsSelected = false; + // } + } + return LayerIndex; +} + +static void +Layer_RecursiveDeselect(memory *Memory, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 TargetIndex, uint16 PrincipalIndex) +{ + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, PrincipalIndex); + sorted_comp_array SortedCompStart = SortedCompArray[PrincipalIndex]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, PrincipalIndex); + for (int i = 0; i < SortedCompStart.LayerCount; i++) { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsPrecomp) { + Layer_RecursiveDeselect(Memory, SortedCompArray, SortedLayerArray, TargetIndex, Layer->Block_Source_Index); + } + if (Layer->Block_Composition_Index != TargetIndex) { + Layer->IsSelected = false; + } + } +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a3b3724 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,915 @@ +#include <glad.h> + +#include <stdio.h> +#if WINDOWS +#include <windows.h> +#else +#include <sys/mman.h> +#include <unistd.h> +#endif + +#if ARM +#include <arm_neon.h> +#include <arm_sve.h> +#else +#include <smmintrin.h> +#endif + +#include "imgui.h" +#include "imgui_impl_sdl.h" +#include "imgui_impl_opengl3.h" +#include <SDL.h> +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include <SDL_opengles2.h> +#else +#include <SDL_opengl.h> +#endif + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_FAILURE_USERMSG +#include "stb_image.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +// TODO(fox): Used for thumbnails. The renderer could be expanded to do this +// much more efficiently. +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize.h" + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavformat/avio.h> +#include <libavutil/avutil.h> +#include <libswscale/swscale.h> +#include <libswresample/swresample.h> +} + + +#include "defines.h" +#include "my_math.h" +#include "structs.h" +#if STABLE +#include "stable_diffusion.h" +#endif +#include "memory.h" +#include "main.h" +#include "ffmpeg_backend.h" + +#include "layer.h" +#include "debug.h" +#include "functions.h" + +SDL_Thread *Thread[8]; +SDL_sem *Semaphore; + +SDL_atomic_t CurrentEntry; +SDL_atomic_t QueuedEntries; +SDL_atomic_t CompletedEntries; + +render_entry Entries[256]; + +static uint64 BitmapBlockSize; +static instruction_mode InstructionMode = instruction_mode_scalar; +static uint32 RandomGlobalIncrement = 0; + +uint32 BitmapFill = 0x00000001; + +#if STABLE +#include "base64.c" +#include <curl/curl.h> +#endif + +#include "memory.cpp" +#include "undo.cpp" +#include "io.cpp" +#include "sorted.cpp" +#include "layer.cpp" +#include "strings.cpp" +#include "threading.cpp" +#include "createcalls.cpp" +#if STABLE +#include "stable_diffusion.cpp" +#endif +#include "ffmpeg_backend.cpp" +#include "imgui_ui.cpp" +#include "prenderer.cpp" +#include "gl_calls.cpp" +#include "bezier.cpp" +#include "effects_gl_shader.cpp" +#include "effects.cpp" +#include "effects_constructors.cpp" + +static void +Main_RenderUI(ImGuiIO io, ImVec4 clear_color, SDL_Window *window) +{ + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); +} + +static void +Main_InputTest(project_data *File, project_state *State, memory *Memory, sorted_file Sorted, ui *UI, SDL_Window *window, GLuint textureID) +{ + ImGuiIO& io = ImGui::GetIO(); + SDL_Event event = {}; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_DROPFILE) { + char *DropFile = event.drop.file; + Source_Generate(File, State, Memory, DropFile); + SDL_free(DropFile); + } + if (event.type == SDL_QUIT) + State->IsRunning = false; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + State->IsRunning = false; + } + + if (State->Warp_WantSetPos) { + ImGui::GetIO().WantSetMousePos = true; + io.MousePos = State->Warp_PositionToSet; + } + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + + ImGui::NewFrame(); + + if (State->Warp_WantSetPos) { + ImGui_WarpMouseFinish(State, io.MousePos); + io.MouseDelta = {}; + State->Warp_WantSetPos = false; + } + + if (!io.WantCaptureKeyboard) + ImGui_ProcessInputs(File, State, UI, Memory, io, Sorted); + + ImGui::DockSpaceOverViewport(); + +#if DEBUG + if (Debug.ToggleWindow) { + ImGui::ShowDemoWindow(); + ImGui_DebugRenderQueue(State); + ImGui_DebugMemoryViewer(Memory, State); + ImGui_DebugUndoTree(Memory, State); + } +#endif + + ImGui_Viewport(File, State, UI, Memory, io, textureID, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyArray); + ImGui_Timeline(File, State, Memory, UI, io, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyStart, Sorted.PropertyArray); + ImGui_File(File, State, Memory, io, Sorted.CompArray, Sorted.LayerArray); + ImGui_PropertiesPanel(File, State, UI, Memory, io, Sorted.CompArray, Sorted.LayerArray, Sorted.PropertyStart, Sorted.PropertyArray); + ImGui_ColorPanel(File, State, UI, Memory, io); + ImGui_EffectsPanel(File, State, Memory, UI, io); +#if STABLE + if (UI->StableEnabled) { + ImGui_SD_Prompt(File, State, UI, Memory, io, Sorted.CompArray, Sorted.LayerArray); + ImGui_SD_Thumbnail(File, State, UI, Memory, io, Sorted.CompArray, Sorted.LayerArray, Sorted.SourceArray, Sorted.TempSourceCount); + } +#endif + ImGui_Menu(File, State, UI, Memory, io); + ImGui_Popups(File, State, UI, Memory, io); + +#if DEBUG + Debug.Temp = {}; +#endif + + ImGui::EndFrame(); + + if (Memory->PurgeCache) { + Memory_Cache_Purge(File, State, Memory); + Memory->PurgeCache = false; + } +} + +static void +Render_Main(project_data *File, project_state *State, memory *Memory, sorted_file Sorted, ui *UI, SDL_Window *window, GLuint textureID, + void *Data, void *OutputBuffer, render_type RenderType, rectangle RenderRegion) +{ + bool32 IsRendering = true; + Renderer_Start(Data, OutputBuffer, RenderType, RenderRegion); + while (IsRendering) { + Main_InputTest(File, State, Memory, Sorted, UI, window, textureID); + // ImGuiIO& io = ImGui::GetIO(); + // Main_RenderUI(io, ImVec4(0.45f, 0.55f, 0.60f, 1.00f), window); + Renderer_Check(&IsRendering, RenderType); + } +} + +static void +Layer_UpdateAllKeyframes(project_data *File, project_state *State, memory *Memory, block_layer *Layer, uint16 Index_Physical, + sorted_property_array *SortedProperty, uint16 *SortedKeyframe, uint16 Frame_Current) +{ + int32 Offset = (State->Interact_Active == interact_type_keyframe_move) ? (int32)State->Interact_Offset[0] : 0; + int h = 0, c = 0, p = 0; + property_channel *Property = NULL; + while (Layer_LoopChannels(State, Memory, &SortedProperty, &SortedKeyframe, Layer, &Property, NULL, &h, &c, &p)) + { + Assert(Property); + if (Property->Block_Bezier_Count) { + real32 MinY, MaxY; + Property_MinMax_Y(Memory, State, Property, SortedProperty, &MinY, &MaxY); + real32 Y_Increment = 1 / (MaxY - MinY); + v2 FirstPointPos[3]; + bezier_point *FirstPointAddress = Bezier_LookupAddress(Memory, Property, SortedKeyframe[0]); + Bezier_Interact_Evaluate(State, FirstPointAddress, FirstPointPos); + v2 LastPointPos[3]; + bezier_point *LastPointAddress = Bezier_LookupAddress(Memory, Property, SortedKeyframe[Property->Keyframe_Count - 1]); + Bezier_Interact_Evaluate(State, LastPointAddress, LastPointPos); + if (FirstPointPos[0].x >= Frame_Current) { + Property->CurrentValue = FirstPointPos[0].y; + } else if (LastPointPos[0].x <= Frame_Current) { + Property->CurrentValue = LastPointPos[0].y; + } else { + int KeyframeIndex = 0; + for (;;) { + v2 PointPos[3]; + bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, SortedKeyframe[KeyframeIndex + 1]); + Bezier_Interact_Evaluate(State, PointAddress, PointPos, 1, Y_Increment); + if (PointPos[0].x >= Frame_Current) + break; + KeyframeIndex++; + } + v2 PointPos[3]; + bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, SortedKeyframe[KeyframeIndex]); + Bezier_Interact_Evaluate(State, PointAddress, PointPos, 1, Y_Increment); + v2 NextPointPos[3]; + bezier_point *NextPointAddress = Bezier_LookupAddress(Memory, Property, SortedKeyframe[KeyframeIndex + 1]); + Bezier_Interact_Evaluate(State, NextPointAddress, NextPointPos, 1, Y_Increment); + if (PointAddress->Type == interpolation_type_hold) { + Property->CurrentValue = PointPos[0].y; + } else if (PointAddress->Type == interpolation_type_linear && NextPointAddress->Type == interpolation_type_linear) { + real32 Ratio = (Frame_Current - PointPos[0].x) / (NextPointPos[0].x - PointPos[0].x); + Property->CurrentValue = PointPos[0].y + ((NextPointPos[0].y - PointPos[0].y) * Ratio); + } else { + Property->CurrentValue = Bezier_SolveYForX(PointPos[0], PointPos[0] + PointPos[2], NextPointPos[0] + NextPointPos[1], NextPointPos[0], Frame_Current); + } + } + } + } +} + +static av_info * +AV_Retrieve(project_state *State, memory *Memory, uint32 SourceIndex) +{ + av_info *AV = NULL; + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, P_AVInfo, State->AVCount, &h, &c, &i)) { + av_info *TestAV = (av_info *)Memory_Block_AddressAtIndex(Memory, P_AVInfo, i); + if (TestAV->Block_Source_Index == SourceIndex) { + AV = TestAV; + break; + } + } + return AV; +} + +static void +File_RetrieveBitmap(project_data *File, project_state *State, memory *Memory, block_source *Source, int32 FrameToSeek, void *Bitmap, uint32 LayerIndex) +{ +} + +static void * +Render_Comp(project_data *File, project_state *State, memory *Memory, sorted_file Sorted, ui *UI, SDL_Window *window, GLuint textureID, ImGuiIO io, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray, uint32 CompIndex, int32 Frame_Current) +{ + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex); + cache_entry *Entry_Main = Memory_Cache_Search(State, Memory, cache_entry_type_comp, CompIndex, Frame_Current); + void *CompBuffer = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry_Main->Block_StartIndex); + uint64 Size = Comp->Width * Comp->Height * Comp->BytesPerPixel; + + if (Entry_Main->IsCached) + return CompBuffer; + + Arbitrary_Zero((uint8 *)CompBuffer, Size); + + uint64 Comp_TimeStart = GetCPUTime(); + + sorted_comp_array *SortedCompStart = &SortedCompArray[CompIndex]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); + + for (int i = 0; i < SortedCompStart->LayerCount; i++) { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->Frame_Start <= Frame_Current && + Layer->Frame_End > Frame_Current && Layer->IsVisible) + { + if (State->UpdateKeyframes) { + sorted_property_array *SortedLayerProperties = SortedPropertyStart + SortEntry.SortedPropertyStart; + uint16 *SortedLayerKeyframes = SortedKeyframeArray + SortEntry.SortedKeyframeStart; + Layer_UpdateAllKeyframes(File, State, Memory, Layer, Index_Physical, SortedLayerProperties, SortedLayerKeyframes, Frame_Current); + } + + layer_bitmap_state *BitmapState = &State->Render.Bitmap[Index_Physical]; + void *BitmapAddress = NULL; + void *RenderAddress = NULL; // result of masking, effects, and intermediate paint stroke + int Width = 0, Height = 0, BytesPerPixel = 0; + + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + av_info *AV = NULL; + if (Source->Type == source_type_file) { + AV = AV_Retrieve(State, Memory, Layer->Block_Source_Index); + if (!AV) { + AV = (av_info *)Memory_Block_AllocateAddress(Memory, P_AVInfo); + AV->Occupied = 1; + AV->Block_Source_Index = Layer->Block_Source_Index; + AV_Init(Source, AV, Memory); + State->AVCount++; + } + } + if (Source->HasAudio && !Source->HasVideo) { + Assert(Source->Type == source_type_file); + if (State->AudioLayerIndex == -1) + State->AudioLayerIndex = Index_Physical; + continue; + } + // if ((State->PreviewSource != -1) && Layer->IsSelected) { + // Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, State->PreviewSource); + // } else { + // } + if (Source->Type == source_type_principal || Source->Type == source_type_principal_temp) { + BitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index, 0); + } else { + cache_entry *CacheEntry = Memory_Cache_Search(State, Memory, cache_entry_type_source, Layer->Block_Source_Index, State->Frame_Current); + BitmapAddress = Memory_Block_Bitmap_AddressAtIndex(Memory, CacheEntry->Block_StartIndex); + if (!CacheEntry->IsCached) { + int FrameToSeek = State->Frame_Current - Layer->Frame_Start; + AV_LoadVideoFrame(Memory, Source, AV, FrameToSeek, BitmapAddress); + CacheEntry->IsCached = true; + } + /* + cache_entry *Entry = Memory_Cache_Search(State, Memory, cache_entry_type_source, Layer->Block_Source_Index, 0); + if (!Entry->IsCached) { + uint64 Src_TimeStart = GetCPUTime(); + block_string *Name = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); + int w = 0, h = 0; + void *temp = stbi_load(Name->Char, &w, &h, NULL, 4); + Source->Width = w; + Source->Height = h; + Source->BytesPerPixel = 4; + uint64 Size = Source->Width * Source->Height * Source->BytesPerPixel; + void *Source_Address = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry->Block_StartIndex); + Memory_Copy((uint8 *)Source_Address, (uint8 *)temp, Size); + Bitmap_SRGBToLinear(Source_Address, Source->Width, Source->Height, Source->BytesPerPixel, 1); + stbi_image_free(temp); + BitmapState->ToUpdate = false; + BitmapState->CurrentFrame = 0; + Entry->CycleTime = GetCPUTime() - Src_TimeStart; + Layer->x.CurrentValue = Comp->Width/2; + Layer->y.CurrentValue = Comp->Height/2; + Entry->IsCached = true; + } + BitmapAddress = Memory_Block_Bitmap_AddressAtIndex(Memory, Entry->Block_StartIndex); + */ + } + Width = Source->Width; + Height = Source->Height; + BytesPerPixel = Source->BytesPerPixel; + } else { + block_composition *Precomp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + BitmapAddress = Render_Comp(File, State, Memory, Sorted, UI, window, textureID, io, SortedCompArray, SortedLayerArray, + SortedPropertyStart, SortedKeyframeArray, Layer->Block_Source_Index, (int32)Layer->time.CurrentValue); + Width = Precomp->Width; + Height = Precomp->Height; + BytesPerPixel = Precomp->BytesPerPixel; + } + + Assert(BitmapAddress); + + uint64 ScratchSize = Width * Height * BytesPerPixel; + RenderAddress = Memory_PushScratch(Memory, ScratchSize); + Memory_Copy((uint8 *)RenderAddress, (uint8 *)BitmapAddress, ScratchSize); + + if (State->Interact_Active == interact_type_brush && State->Brush.LayerToPaint_Index == Index_Physical) { + // TODO(fox): Do all these extra precomputes really make a difference in the renderer? + rectangle RenderRegion = { 0, 0, Width, Height }; + direct_info Info = { (real32)Width, (real32)Height, (real32)BytesPerPixel, (real32)Width * BytesPerPixel, + Bitmap_ByteInfo(BytesPerPixel), UI->Color.a, blend_normal, + RenderRegion, State->Brush.TransientBitmap, 0, 0}; + Render_Main(File, State, Memory, Sorted, UI, window, textureID, (void *)&Info, RenderAddress, render_type_notransform, State->Brush.CacheBounds); + } + if (Layer->Block_Effect_Count || Layer->Block_Mask_Count) { + Layer_UpdateMasksEffects(State, Layer, Memory, RenderAddress, Width, Height, BytesPerPixel); + } + + Assert(Width && Width <= 2048); + Assert(Height && Height <= 2048); + transform_info T = Transform_Calculate(State, Memory, File, Layer, Comp, Width, Height, BytesPerPixel); + T.SourceBuffer = RenderAddress; + rectangle RenderRegion = {0, 0, Comp->Width, Comp->Height}; + + Render_Main(File, State, Memory, Sorted, UI, window, textureID, (void *)&T, CompBuffer, render_type_main, RenderRegion); + Memory_PopScratch(Memory, ScratchSize); + } + } + Entry_Main->CycleTime = GetCPUTime() - Comp_TimeStart; + Entry_Main->IsCached = true; + + if (CompIndex == File->PrincipalCompIndex) + State->CachedFrameCount++; + + return CompBuffer; +} + +static void +Render_Paint(project_data *File, project_state *State, memory *Memory, sorted_file Sorted, ui *UI, SDL_Window *window, GLuint textureID, ImGuiIO io, v2 LayerPos) +{ + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->Brush.LayerToPaint_Index); + layer_transforms T_Layer = Layer_GetTransforms(Layer); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + void *SourceBitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index, 0); + + brush_info B; + Brush_Info(&B, &State->Brush, Source, SourceBitmapAddress, LayerPos, UI->Color); + if (State->Brush.Size >= 128) { + Render_Main(File, State, Memory, Sorted, UI, window, textureID, (void *)&B, State->Brush.TransientBitmap, render_type_brush, B.LayerBounds); + } else { + PaintTest(B, State->Brush.TransientBitmap, B.LayerBounds); + } +} + +static void +Render_Blit(project_data *File, project_state *State, memory *Memory, sorted_file Sorted, ui *UI, SDL_Window *window, GLuint textureID, ImGuiIO io, v2 LayerPos) +{ + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->Brush.LayerToPaint_Index); + layer_transforms T_Layer = Layer_GetTransforms(Layer); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + void *SourceBitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index, 0); + rectangle RenderRegion = { 0, 0, Source->Width, Source->Height }; + direct_info Info = { (real32)Source->Width, (real32)Source->Height, (real32)Source->BytesPerPixel, (real32)Source->Width * Source->BytesPerPixel, + Bitmap_ByteInfo(Source->BytesPerPixel), UI->Color.a, blend_normal, + RenderRegion, State->Brush.TransientBitmap, 1, 0}; + Render_Main(File, State, Memory, Sorted, UI, window, textureID, (void *)&Info, SourceBitmapAddress, render_type_notransform_swap, State->Brush.CacheBounds); + uint64 BitmapSize = Source->Width * Source->Height * Source->BytesPerPixel; + History_Entry_Commit(Memory, "Paintbrush stroke"); + History_Action_BitmapPaint(Memory, BitmapSize, SourceBitmapAddress, State->Brush.TransientBitmap, Source->BytesPerPixel); + History_Entry_End(Memory); + State->Brush.LayerToPaint_Index = -1; + State->Brush.PrevPos = V2(-4000, -4000); + State->Brush.CacheBounds = { 99999, 99999, -99999, -99999 }; + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + State->UpdateFrame = true; +} + +static void +Main_Renderer(project_data *File, project_state *State, memory *Memory, sorted_file Sorted, ui *UI, SDL_Window *window, GLuint textureID, ImGuiIO io) +{ + State->UpdateFrame = false; + + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + + void *MainCompBuffer = Render_Comp(File, State, Memory, Sorted, UI, window, textureID, io, Sorted.CompArray, Sorted.LayerArray, + Sorted.PropertyStart, Sorted.PropertyArray, File->PrincipalCompIndex, State->Frame_Current); + // Bitmap_SRGBToLinear(MainCompBuffer, MainComp->Width, MainComp->Height, MainComp->BytesPerPixel, 0); + glBindTexture(GL_TEXTURE_2D, textureID); + + int ByteFlag2 = (MainComp->BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT; + if (State->FirstFrame) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MainComp->Width, MainComp->Height, 0, GL_RGBA, ByteFlag2, MainCompBuffer); + State->FirstFrame = false; + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, MainComp->Width, MainComp->Height, GL_RGBA, ByteFlag2, MainCompBuffer); + + // TODO(fox): garbage collect AV state! + + State->UpdateKeyframes = false; + +} + + +int main(int argc, char *argv[]) { + + global_memory GlobalMemory = {}; + + GlobalMemory.Size = ((uint64)1 * 1024 * 1024 * 1024); + GlobalMemory.CurrentPosition = 0; + +#if WINDOWS + GlobalMemory.Address = VirtualAlloc(0, GlobalMemory.Size, + MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#else + GlobalMemory.Address = mmap(0, GlobalMemory.Size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); +#endif + + // 1 meg per block + BitmapBlockSize = 1024 * 1024; + + memory Memory = {}; + + Memory_InitTable(&GlobalMemory, &Memory, 1 * 1024 * 1024, P_AVInfo, "FFmpeg state", sizeof(av_info)); + Memory_InitTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, P_UndoBuffer, "Undo buffer"); + Memory_InitTable(&GlobalMemory, &Memory, 40 * 1024 * 1024, P_MiscCache, "Misc persistent"); + + Memory_InitTable(&GlobalMemory, &Memory, sizeof(project_data), F_File, "File", sizeof(project_data)); + Memory_InitTable(&GlobalMemory, &Memory, 1 * 1024 * 1024, F_Precomps, "Precomps", sizeof(block_composition)); + Memory_InitTable(&GlobalMemory, &Memory, 2 * 1024 * 1024, F_Layers, "Layers", sizeof(block_layer)); + Memory_InitTable(&GlobalMemory, &Memory, 1 * 1024 * 1024, F_Sources, "Sources", sizeof(block_source)); + Memory_InitTable(&GlobalMemory, &Memory, 2 * 1024 * 1024, F_Effects, "Properties", sizeof(block_effect)); + Memory_InitTable(&GlobalMemory, &Memory, 2 * 1024 * 1024, F_Properties, "Properties", sizeof(property_channel)); + Memory_InitTable(&GlobalMemory, &Memory, 4 * 1024 * 1024, F_Bezier, "Bezier paths (keyframes, masks)", sizeof(block_bezier)); + Memory_InitTable(&GlobalMemory, &Memory, 4 * 1024 * 1024, F_Strings, "Strings", sizeof(block_string)); + Memory_InitTable(&GlobalMemory, &Memory, (uint64)100 * 1024 * 1024, F_PrincipalBitmaps, "Principal bitmap data", BitmapBlockSize); + + Memory_InitTable(&GlobalMemory, &Memory, (uint64)5 * 1024 * 1024, B_Thumbnails, "Thumbnails"); + Memory_InitTable(&GlobalMemory, &Memory, (uint64)64 * 1024 * 1024, B_ScratchSpace, "Scratch"); + // Memory_InitTable(&GlobalMemory, &Memory, (uint64)1 * 1024 * 1024, B_CacheEntries, "Cache entries", sizeof(cache_entry)); + Memory_InitTable(&GlobalMemory, &Memory, (uint64)700 * 1024 * 1024, B_CachedBitmaps, "Cached bitmap buffer"); + + +#if ARM + InstructionMode = instruction_mode_neon; +#else + // if (SDL_HasSSE2()) { + // InstructionMode = instruction_mode_sse; + // } + if (SDL_HasAVX2()) { + InstructionMode = instruction_mode_avx; + } +#endif + + int SamplesPerSecond = 48000; + int BytesPerSample = sizeof(int16) * 2; + + project_state State_ = {}; + project_state *State = &State_; + project_data *File = (project_data *)Memory_Block_AllocateAddress(&Memory, F_File); + *File = {}; + File->Occupied = 1; + + // NOTE(fox): Right now I'm just gonna throw all dynamic allocs that can't + // be simplified to the push/pop model here; will worry about how to best + // use RAM later. + + State->Brush.PaintBuffer = (void *)((uint8 *)Memory.Slot[B_ScratchSpace].Address + Memory.ScratchPos); + uint64 ScratchPaintSize = 2048*2048*4; + Memory.ScratchPos += ScratchPaintSize; + State->Brush.TransientBitmap = (void *)((uint8 *)Memory.Slot[B_ScratchSpace].Address + Memory.ScratchPos); + ScratchPaintSize = 2048*2048*4; + Memory.ScratchPos += ScratchPaintSize; + State->Dump1 = (void *)((uint8 *)Memory.Slot[B_ScratchSpace].Address + Memory.ScratchPos); + Memory.ScratchPos += ScratchPaintSize; + State->Dump2 = (void *)((uint8 *)Memory.Slot[B_ScratchSpace].Address + Memory.ScratchPos); + uint32 AudioBufferSize = 48000 * sizeof(uint16) * 60; + void *AudioData = (void *)((uint8 *)Memory.Slot[B_ScratchSpace].Address + Memory.ScratchPos); + Memory.ScratchPos += AudioBufferSize; + + State->ClipboardBuffer = (void *)((uint8 *)Memory.Slot[B_ScratchSpace].Address + Memory.ScratchPos); + State->ClipboardSize = 1024*1024; + Memory.ScratchPos += State->ClipboardSize; + + State->Test = ImDrawListSplitter(); + + block_composition *MainComp = (block_composition *)Memory_Block_AllocateAddress(&Memory, F_Precomps); + + MainComp->Width = 1280; + MainComp->Height = 720; + MainComp->FPS = 60; + MainComp->BytesPerPixel = 4; + MainComp->Frame_Count = 80; + MainComp->Frame_End = 80; + MainComp->Occupied = 1; + MainComp->Name_String_Index = String_AddToFile(&Memory, "Main comp"); + + File->Comp_Count = 1; + + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); + + SDL_AudioSpec Audio = {0}; + + //AUDIO + Audio.freq = 48000; + Audio.format = AUDIO_S16LSB; + Audio.channels = 2; + Audio.samples = SamplesPerSecond * BytesPerSample / 60; + + SDL_OpenAudio(&Audio, 0); + + Assert(Audio.format == AUDIO_S16LSB); + + SDL_PauseAudio(1); + + Semaphore = SDL_CreateSemaphore(0); + + int Index[7]; + for (int i = 0; i < 7; i++) { + Index[i] = i; + Thread[i] = SDL_CreateThread(TestThread, "thread", (void *)&Index[i]); + } + + // Decide GL+GLSL versions + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 + const char* glsl_version = "#version 100"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#elif defined(__APPLE__) + // GL 3.2 Core + GLSL 150 + const char* glsl_version = "#version 150"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#endif + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + // SDL_RenderSetScale(renderer, 2, 2) +#if DEBUG +#if ARM + uint32 ScreenSize[2] = {(uint32)(2560/1.2), (uint32)(1600/1.2)}; +#else + real32 ScreenSize[2] = {3840/1.2, 2160/1.2}; +#endif +#else + real32 ScreenSize[2]; + SDL_DisplayMode current; + int windowtest = SDL_GetCurrentDisplayMode(0, ¤t); + if (windowtest == 0) { + ScreenSize[0] = current.w; + ScreenSize[1] = current.h; + } else { + ScreenSize[0] = 1920; + ScreenSize[1] = 1080; + } +#endif + SDL_Window* window = SDL_CreateWindow("Event Tester", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, ScreenSize[0], ScreenSize[1], window_flags); + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + + if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) + { + printf("Failed to initialize GLAD"); + return -1; + } + + GL_InitDefaultShader(); + GL_InitDefaultVerts(); + + Effect_InitEntries(State); + + SDL_GL_MakeCurrent(window, gl_context); + + SDL_Event Event; + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_NavEnableSetMousePos; + (void)io; + + // NOTE(fox): Instead of constructing the position of the windows on + // startup with Docking API calls (which is experimental and incomplete) + // I'm loading the window positions from this convenient tool. ImGui by + // default saves window position to an external .ini file, which can be + // loaded from disk or memory. + // io.IniFilename = NULL; + // ImGui::LoadIniSettingsFromMemory(ImGuiPrefs); + // ImGui::SaveIniSettingsToDisk("imgui.ini"); + + ImGui::StyleColorsDark(); + + ImGui::PushStyleColor(ImGuiCol_ModalWindowDimBg, + ImGui::ColorConvertFloat4ToU32(ImVec4(0.0f, 0.0f, 0.0f, 0.1f))); + + ImGui_ImplSDL2_InitForOpenGL(window, gl_context); + ImGui_ImplOpenGL3_Init(glsl_version); + + GLuint textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + Brush_CalcBitmapAlphaFromSize(&Memory, &State->Brush, 4); + State_BindBrushTexture(&Memory, &State->Brush, 4); + +#if STABLE + curl_global_init(CURL_GLOBAL_ALL); + curl_state MainHandle = {}; + curl_state ProgHandle = {}; +#endif + +#if DEBUG +#if 1 + sprintf(State->DummyName, "test"); + File_Open(File, State, &Memory, State->DummyName); + State->UpdateFrame = true; + State->MostRecentlySelectedLayer = 0; + uint16 SourceIndex = Source_Generate(File, State, &Memory, (void *)"../asset/24.mp4"); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, SourceIndex); + Source->IsSelected = true; + Source_UICreateButton(File, State, &Memory); +#endif +#endif + + while (State->IsRunning) + { + uint64 PerfFrequency = SDL_GetPerformanceFrequency(); + uint64 PerfStart = SDL_GetPerformanceCounter(); + +#if STABLE + if (State->CurlActive) { + Curl_Main(File, State, &Memory, &MainHandle, &ProgHandle); + } +#endif + + // NOTE(fox): These commands affect sorting, and should not be executed + // in any UI or the renderer. + if (State->HotkeyInput) { + switch (State->HotkeyInput) { + case hotkey_none: + { + Assert(0); + } break; + // case hotkey_effect_indexup: + // { + // History_Entry_Commit(Memory, "Swap effect order"); + // History_Action_Swap(Memory, F_Effects, sizeof(Effect->Index), &Effect->Index); + // Effect->Index += 1; + // uint16 NextEffectIdx = Layer->Block_Effect_Index[h - AmountOf(Layer->Property) + 1]; + // block_effect *NextEffect = (block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, NextEffectIdx, 0); + // History_Action_Swap(Memory, F_Effects, sizeof(NextEffect->Index), &NextEffect->Index); + // NextEffect->Index -= 1; + // History_Entry_End(Memory); + // } break; + case hotkey_newpaintlayer: + { + Project_PaintLayer_New(File, State, &Memory); + } break; + case hotkey_newlayerfromsource: + { + Source_UICreateButton(File, State, &Memory); + State->UpdateKeyframes = true; + } break; + case hotkey_deletelayer: + { + Project_Layer_Delete(File, State, &Memory); + } break; + case hotkey_undo: + { + History_Undo(&Memory); + Memory_Cache_Purge(File, State, &Memory); + State->UpdateFrame = true; + } break; + case hotkey_redo: + { + History_Redo(&Memory); + Memory_Cache_Purge(File, State, &Memory); + State->UpdateFrame = true; + } break; + default: + { + Assert(0); + } break; + } + State->HotkeyInput = hotkey_none; + } + + sorted_file Sorted = File_Sort_Push(File, State, &Memory); + + Main_InputTest(File, State, &Memory, Sorted, &File->UI, window, textureID); + + if (State->IsPlaying) { + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(&Memory, F_Precomps, File->PrincipalCompIndex); + Playhead_Increment(&State->Frame_Current, MainComp->Frame_Start, MainComp->Frame_End, 1); + State->UpdateFrame = true; + State->UpdateKeyframes = true; + } + + if ((State->AudioLayerIndex != -1) && + (State->CachedFrameCount == (MainComp->Frame_End - MainComp->Frame_Start))) + { + block_layer *AudioLayer = (block_layer *)Memory_Block_AddressAtIndex(&Memory, F_Layers, State->AudioLayerIndex); + // block_source *AudioSource = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, SourceIndex); + av_info *AV = AV_Retrieve(State, &Memory, AudioLayer->Block_Source_Index); + int32 LayerPos = AudioLayer->Frame_Start; + + if (State->Frame_Current >= LayerPos) { + if (State->Frame_Current == LayerPos) { + SDL_ClearQueuedAudio(1); + AV_SeekAudio(AV, 60, 0); + } + if (State->HotFramePerf == 1) { + SDL_ClearQueuedAudio(1); + int32 FrameToSeek = State->Frame_Current - LayerPos; + if (FrameToSeek > -1) + AV_SeekAudio(AV, 60, FrameToSeek); + } + + uint32 QueuedAudioSize = SDL_GetQueuedAudioSize(1); + int TargetAudioSize = SamplesPerSecond * BytesPerSample / 2; + if (QueuedAudioSize < (TargetAudioSize / 2)) + { + int BytesToWrite = TargetAudioSize - QueuedAudioSize; + printf("%i bytes in queue.\n", QueuedAudioSize); + int BytePlayhead = 0; + while (BytePlayhead < BytesToWrite) { + uint8 *Data = (uint8 *)AudioData + BytePlayhead; + int SampleCount = AV_AudioTest(AV, Data, AudioBufferSize - BytePlayhead); + int Size = SampleCount * BytesPerSample; + BytePlayhead += Size; + } + SDL_QueueAudio(1, AudioData, BytePlayhead); + printf("Queued %i bytes.\n", BytePlayhead); + } + } else if (SDL_GetQueuedAudioSize(1)) { + SDL_ClearQueuedAudio(1); + } + } + + if (State->UpdateFrame) { + // Default queue item type simply calls the renderer on the current + // frame, so no additional info is needed + State->Queue.CurrentIdx++; + } + for (int i = 0; i < State->Queue.CurrentIdx; i++) { + render_queue_item Item = State->Queue.Item[i]; + State->Queue.Playhead = i; + if (Item.Type == 0) { + if (State->Interact_Active) { + Memory_Cache_Purge(File, State, &Memory, State->Frame_Current); + } + Main_Renderer(File, State, &Memory, Sorted, &File->UI, window, textureID, io); + } else if (Item.Type == 1) { + Assert(State->Interact_Active == interact_type_brush); + Render_Paint(File, State, &Memory, Sorted, &File->UI, window, textureID, io, Item.Pos); + } else if (Item.Type == 2) { + Assert(State->Interact_Active == interact_type_brush); + Render_Blit(File, State, &Memory, Sorted, &File->UI, window, textureID, io, Item.Pos); + } else { + Assert(0); + } + } + State->Queue.CurrentIdx = 0; + State->Queue.Playhead = 0; + Arbitrary_Zero((uint8 *)State->Queue.Item, sizeof(State->Queue.Item)); + + File_Sort_Pop(&Memory, Sorted.Layer_SortSize, Sorted.Property_SortSize, Sorted.Source_SortSize); + + Assert(Debug.ScratchState == 0); + + if (State->IsPlaying && State->HotFramePerf > 1) { + uint64 RenderTime = SDL_GetPerformanceCounter() - State->HotFramePerf; + real64 FrameMS = (1000.0f * (real64)RenderTime) / (real64)PerfFrequency; + real64 TargetMS = (1000.0f / MainComp->FPS); + if (TargetMS > FrameMS) + SDL_Delay((uint64)(TargetMS - FrameMS)); + Main_RenderUI(io, clear_color, window); + State->HotFramePerf = 1; + } else { + Main_RenderUI(io, clear_color, window); + } + + if (State->HotFramePerf == 1) { + State->HotFramePerf = SDL_GetPerformanceCounter(); + } + + if (State->Initializing) + State->Initializing--; + + uint64 PerfEnd = SDL_GetPerformanceCounter(); + uint64 PerfTime = PerfEnd - PerfStart; + real64 FrameMS = (1000.0f * (real64)PerfTime) / (real64)PerfFrequency; + real64 FPS = PerfFrequency / PerfTime; + // printf("%.02f ms/f, %.02f frames\n", FrameMS, FPS); + } + + for (int i = 0; i < 7; i++) { + SDL_DetachThread(Thread[i]); + } + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + SDL_Quit(); + + return 0; +} diff --git a/src/memory.cpp b/src/memory.cpp new file mode 100644 index 0000000..05254cb --- /dev/null +++ b/src/memory.cpp @@ -0,0 +1,386 @@ + +static void +Memory_InitTable(global_memory *GlobalMemory, memory *Memory, uint64 Size, memory_table_list TableName, char *Name, uint64 Block_ElementSize = 0) { + memory_table *Table = &Memory->Slot[TableName]; + Table->Name = Name; + Table->Address = (ptrsize *)((uint8 *)GlobalMemory->Address + GlobalMemory->CurrentPosition); + Table->Size = Size; + Table->Block_ElementSize = Block_ElementSize; + GlobalMemory->CurrentPosition += Size; +} + +static uint32 +Memory_Block_AllocateNew(memory *Memory, memory_table_list TableName) +{ + memory_table *Table = &Memory->Slot[TableName]; + Assert(Table->Block_ElementSize != 0); + bool32 Empty = 0; + uint32 Index = 0; + uint8 *Address_Playhead = (uint8 *)Table->Address; + while (*Address_Playhead != 0) { + Address_Playhead += Table->Block_ElementSize; + Index++; + } + Arbitrary_Zero(Address_Playhead, Table->Block_ElementSize); + + return Index; +} + +static void * +Memory_Block_AddressAtIndex(memory *Memory, memory_table_list TableName, uint32 Index, bool32 AssertExists = 1) +{ + memory_table *Table = &Memory->Slot[TableName]; + Assert(Table->Block_ElementSize != 0); + uint8 *Address = (uint8 *)Table->Address + (Table->Block_ElementSize * Index); + if (AssertExists) + Assert(*Address != 0); + return (void *)Address; +} + +static uint16 +Memory_Block_LazyIndexAtAddress(memory *Memory, memory_table_list TableName, void *Address) +{ + memory_table *Table = &Memory->Slot[TableName]; + return ((uint8 *)Address - (uint8 *)Table->Address) / Table->Block_ElementSize; +} + +static void * +Memory_Block_AllocateAddress(memory *Memory, memory_table_list TableName) +{ + uint16 FileIndex = Memory_Block_AllocateNew(Memory, TableName); + return Memory_Block_AddressAtIndex(Memory, TableName, FileIndex, 0); +} + +// IMPORTANT(fox): All block data structs have to start with a uint8 Occupied variable! +static bool32 +Block_Loop(memory *Memory, memory_table_list TableName, uint32 TotalCount, int *HasIncremented, int *CurrentCount, int *Index) +{ + for (;;) { + if (*CurrentCount == TotalCount) { + return 0; + } + if (*HasIncremented) { + *HasIncremented = 0; + (*Index)++; + } + uint8 *Occupied = (uint8 *)Memory_Block_AddressAtIndex(Memory, TableName, *Index, 0); + if (*Occupied) { + *HasIncremented = 1; + (*CurrentCount)++; + return 1; + } + (*Index)++; + Assert(*CurrentCount <= TotalCount); + Assert(*Index <= TotalCount*100); // This can get triggered normally if 100+ items are added and the first 99 in memory are deleted. + } + Assert(0); + return 0; +} + +static bool32 +Block_Loop(memory *Memory, property_channel *Property, uint32 TotalCount, int *HasIncremented, int *CurrentCount, int *Index) +{ + for (;;) { + if (*CurrentCount == TotalCount) { + return 0; + } + if (*HasIncremented) { + *HasIncremented = 0; + (*Index)++; + } + uint8 *Occupied = (uint8 *)Bezier_LookupAddress(Memory, Property, *Index, 0); + if (*Occupied) { + *HasIncremented = 1; + (*CurrentCount)++; + return 1; + } + (*Index)++; + Assert(*CurrentCount <= TotalCount); + Assert(*Index <= TotalCount*100); // This can get triggered normally if 100+ items are added and the first 99 in memory are deleted. + } + Assert(0); + return 0; +} + +static uint32 +Memory_Block_PrincipalBitmap_AllocateNew(project_data *File, project_state *State, memory *Memory) +{ + int h = 0, c = 0, i = 0; + int MaxBlockIndex = -1; + int PrincipalCount = 0; + while (Block_Loop(Memory, F_Sources, File->Source_Count, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + if (Source->Type == source_type_principal || Source->Type == source_type_principal_temp) + PrincipalCount++; + if (Source->Bitmap_Index > MaxBlockIndex) + MaxBlockIndex = i; + } + if (!PrincipalCount) + return 0; + + block_source Source = *(block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, MaxBlockIndex); + uint32 LastBlock = Source.Bitmap_Index; + uint32 BlockSize = ((Source.Width * Source.Height * Source.BytesPerPixel) / BitmapBlockSize) + 1; + + uint32 Blocks_Max = Memory->Slot[B_CachedBitmaps].Size / BitmapBlockSize; + Assert(Blocks_Max > (LastBlock + BlockSize)); + + return LastBlock + BlockSize; + +} + +static uint32 +Memory_Block_Bitmap_AllocateNew(project_state *State, memory *Memory, cache_entry Entry, uint64 NewSize) +{ + uint32 LastVal = 0; + uint32 LastBlock = 0; + uint32 c = 0; + cache_entry *EntryArray = State->Render.Entry; + while (EntryArray[c].IsOccupied != 0) { + if (EntryArray[c].Block_StartIndex > LastBlock) { + LastBlock = EntryArray[c].Block_StartIndex; + LastVal = c; + } + c++; + } + cache_entry LastEntry = EntryArray[LastVal]; + uint32 LastEntry_BlockCount = 0; + switch (EntryArray[LastVal].Type) { + case cache_entry_type_comp: + { + block_composition Comp = *(block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, LastEntry.TypeInfo); + uint64 Size = Comp.Width * Comp.Height * Comp.BytesPerPixel; + LastEntry_BlockCount = (Size / BitmapBlockSize) + 1; + } break; + case cache_entry_type_source: + { + block_source Source = *(block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, LastEntry.TypeInfo); + uint64 Size = Source.Width * Source.Height * Source.BytesPerPixel; + LastEntry_BlockCount = (Size / BitmapBlockSize) + 1; + } break; + case cache_entry_type_assert: + { + Assert(0); + } break; + default: + { + Assert(0); + } break; + } + + uint32 Blocks_Max = Memory->Slot[B_CachedBitmaps].Size / BitmapBlockSize; + Assert(Blocks_Max > LastBlock); + return LastBlock + LastEntry_BlockCount; + + /* + uint32 Blocks_Needed = (NewSize / BitmapBlockSize) + 1; + uint32 Block_Index_Available = 0; + */ + +} +static void +Memory_Cache_Purge(project_data *File, project_state *State, memory *Memory, int32 SingleFrame = -1) +{ + cache_entry *EntryArray = State->Render.Entry; + int c = 0; + int count = Memory->EntryCount; + while (count != 0) { + bool32 ExtraCheck = (SingleFrame == -1) ? 1 : EntryArray[c].TypeInfo == SingleFrame; + if (EntryArray[c].Type == cache_entry_type_comp && + EntryArray[c].TypeInfo == File->PrincipalCompIndex && SingleFrame) { + EntryArray[c].IsCached = 0; + } + c++; + count--; + } +} + +static cache_entry * +Memory_Cache_Search(project_state *State, memory *Memory, cache_entry_type Type, uint32 TypeInfo, uint32 TypeInfo_Sub) +{ + cache_entry *EntryArray = State->Render.Entry; + int c = 0; + int count = Memory->EntryCount; + while (count != 0) { + if (EntryArray[c].Type == Type && + EntryArray[c].TypeInfo == TypeInfo && + EntryArray[c].TypeInfo_Sub == TypeInfo_Sub) { + return &EntryArray[c]; + } + c++; + count--; + } + if (c != 0) + EntryArray[c].Block_StartIndex = Memory_Block_Bitmap_AllocateNew(State, Memory, EntryArray[c], 0); + EntryArray[c].IsOccupied = true; + EntryArray[c].Type = Type; + EntryArray[c].TypeInfo = TypeInfo; + EntryArray[c].TypeInfo_Sub = TypeInfo_Sub; + Memory->EntryCount++; + return &EntryArray[c]; +} + +static void * +Memory_Block_Bitmap_AddressAtIndex(memory *Memory, uint32 Index) +{ + memory_table *Table = &Memory->Slot[B_CachedBitmaps]; + uint8 *Address = (uint8 *)Table->Address + Index*BitmapBlockSize; + return (void *)Address; +} + +static void * +Memory_PushScratch(memory *Memory, uint64 Size) { + memory_table *Table = &Memory->Slot[B_ScratchSpace]; + uint8 *Address = ((uint8 *)Table->Address + Memory->ScratchPos); + Memory->ScratchPos += Size; +#if DEBUG + Debug.ScratchSize[Debug.ScratchState] = Size; + Debug.ScratchState++; +#endif + return (void *)Address; +} + +static void +Memory_PopScratch(memory *Memory, uint64 Size) { + memory_table *Table = &Memory->Slot[B_ScratchSpace]; + Memory->ScratchPos -= Size; +#if DEBUG + Debug.ScratchState--; + Assert(Debug.ScratchSize[Debug.ScratchState] == Size); +#endif +} + +static void * +Memory_AddressAtOffset(memory *Memory, memory_table_list TableName, uint64 Offset) +{ + memory_table *Table = &Memory->Slot[TableName]; + return (void *)((uint8 *)Table->Address + Offset); +} + +void Memory_Copy(uint8 *Address_Write, uint8 *Address_Read, uint64 Size) +{ + uint64 i = 0; + while (i < Size) { + *(Address_Write + i) = *(Address_Read + i); + i++; + } +} + +void Memory_Fill(uint8 *Address_Write, uint8 *Address_Read, uint64 WriteSize, uint64 ReadSize) +{ + uint64 i = 0; + while (i < WriteSize) { + *(Address_Write + i) = *(Address_Read + (i % ReadSize)); + i++; + } +} + +void Arbitrary_Zero(uint8 *Address_Write, uint64 Size) +{ + uint64 i = 0; + while (i < Size) { + *(Address_Write + i) = 0; + i++; + } +} + +void Arbitrary_SwapData(memory *Memory, uint8 *Address_0, uint8 *Address_1, uint64 Size) +{ + uint8 *Buffer_Scratch = (uint8 *)Memory_PushScratch(Memory, Size); + Memory_Copy(Buffer_Scratch, Address_0, Size); + Memory_Copy(Address_0, Address_1, Size); + Memory_Copy(Address_1, Buffer_Scratch, Size); + Memory_PopScratch(Memory, Size); +} + + +static void +Arbitrary_ShiftData(uint8 *Address_Start, uint8 *Address_End, uint64 ShiftAmount, int32 Direction) +{ + if (Direction > 0) { + uint8 *AddressPlayhead = Address_End; + while ((ptrsize)AddressPlayhead >= (ptrsize)Address_Start) { + *(AddressPlayhead + ShiftAmount) = *AddressPlayhead; + AddressPlayhead--; + } + } else { + uint8 *AddressPlayhead = Address_Start; + while ((ptrsize)AddressPlayhead < (ptrsize)Address_End) { + *(AddressPlayhead - ShiftAmount) = *AddressPlayhead; + AddressPlayhead++; + } + } +} + +// TODO(fox): Just use zlib... +extern "C" { +#include "miniz.c" +} + +static uint64 +Data_Compress(memory *Memory, void *DataSource, uint64 DataSize, void *DataBuffer, uint64 DataBufferSize, int CompressionLevel) +{ + z_stream stream = {}; + stream.next_in = (uint8 *)DataSource; + stream.avail_in = DataSize; + stream.next_out = (uint8 *)DataBuffer; + stream.avail_out = DataBufferSize; + + if (deflateInit(&stream, CompressionLevel) != Z_OK) + { + Assert(0); + } + + int status; + for ( ; ; ) + { + status = deflate(&stream, Z_SYNC_FLUSH); + + if (status == Z_STREAM_END || !stream.avail_in) + break; + else + Assert(0); + } + + uint64 CompressedSize = stream.total_out; + + if (deflateEnd(&stream) != Z_OK) + { + Assert(0); + } + + return CompressedSize; +} + +static void +Data_Decompress(memory *Memory, void *CompressedLocation, uint64 CompressedSize, void *BitmapLocation, uint64 ExpectedSize) +{ + z_stream stream = {}; + stream.next_in = (uint8 *)CompressedLocation; + stream.avail_in = CompressedSize; + stream.next_out = (uint8 *)BitmapLocation; + stream.avail_out = 2147483648; + + if (inflateInit(&stream)) + { + Assert(0); + } + + int status; + for ( ; ; ) + { + status = inflate(&stream, Z_NO_FLUSH); + + if (status == Z_STREAM_END || !stream.avail_in) + break; + else + Assert(0); + } + + // Assert(stream.total_out == ExpectedSize); + + if (inflateEnd(&stream) != Z_OK) + { + Assert(0); + } +} diff --git a/src/paint.cpp b/src/paint.cpp new file mode 100644 index 0000000..f3170e5 --- /dev/null +++ b/src/paint.cpp @@ -0,0 +1,103 @@ + +static void +SlidingBrush(pixel_buffer *Buffer, v2i Pos, brush_tool Brush) +{ + v2i Min = {0,0}; + v2i Max = Min + (int16)Brush.Size; + + v2 Center = {(real32)Max.x / 2.0f, (real32)Max.y / 2.0f}; + real32 MaxLength = sqrt(LengthSq(Center)); + + v2i LayerMin = Pos - (Brush.Size / 2); + v2i LayerMax = Pos + (Brush.Size / 2); + + rectangle LayerBounds = {0, 0, Buffer->Width, Buffer->Height}; + rectangle Paint = ClipRectangle({LayerMin, LayerMax}, LayerBounds); + + uint8 *Row = ((uint8 *)Buffer->OriginalBuffer + Buffer->Pitch*Paint.Min.y); + for(int Y = Paint.Min.y; + Y < Paint.Max.y; + ++Y) + { + uint32 *Pixel = (uint32 *)(Row + Paint.Min.x*sizeof(uint32)); + for(int X = Paint.Min.x; + X < Paint.Max.x; + ++X) + { + v2 Pos = V2(abs(Center.x - (X - Paint.Min.x)), abs(Center.y - (Y - Paint.Min.y))); + real32 L = sqrt(LengthSq(Pos)) + Center.x; + real32 Gradient = pow(Ceil(L, MaxLength) / MaxLength, Brush.Hardness); + RenderAlpha(Pixel, ColToUint32({1.0f, 0.0f, 0.0f, 1.0f - Gradient})); + Pixel++; + } + Row += Buffer->Pitch; + } +} + +/* +static void +Paint(sdl_input Input, project_layer *Layer, brush_tool Brush) +{ + int16 X = Input.Mouse.x - UI.CompX; // convert to comp space + int16 Y = Input.Mouse.y - UI.CompY; + + real32 Rad = (Layer->rotation.CurrentValue.f * (PI / 180)); + real32 s = Layer->scale.CurrentValue.f; + v2 Scale = {Layer->Raster.Width * s, Layer->Raster.Height * s}; + + v2 XAxis = (Layer->Raster.Width * s)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (Layer->Raster.Height * -s)*V2(sin(Rad), -cos(Rad)); + + real32 AnchorX = Layer->ax.CurrentValue.f; + real32 AnchorY = Layer->ay.CurrentValue.f; + + v2 Pos = {Layer->x.CurrentValue.f, Layer->y.CurrentValue.f}; + v2 Origin = Pos - (XAxis * AnchorX) - (YAxis * AnchorY); + + real32 XLengthSq = 1.0f / LengthSq(XAxis); + real32 YLengthSq = 1.0f / LengthSq(YAxis); + + v2 CurrentPixel = V2(X, Y); + v2 StartVector = CurrentPixel - Origin; + + real32 U = XLengthSq*Inner(StartVector, XAxis); + real32 V = YLengthSq*Inner(StartVector, YAxis); + + v2i TexelCoord = {}; + TexelCoord.x = 1.0f + ((U*(real32)(Layer->Raster.Width - 1)) + 0.5f); + TexelCoord.y = 1.0f + ((V*(real32)(Layer->Raster.Height - 1)) + 0.5f); + + v2i Min = {0,0}; + v2i Max = Min + (int16)Brush.Size; + + v2 Center = {(real32)Max.x / 2.0f, (real32)Max.y / 2.0f}; + real32 MaxLength = sqrt(LengthSq(Center)); + + v2i LayerMin = TexelCoord - (Brush.Size / 2); + v2i LayerMax = TexelCoord + (Brush.Size / 2); + + rectangle LayerBounds = {0, 0, Layer->Raster.Width, Layer->Raster.Height}; + rectangle Paint = {LayerMin, LayerMax}; + + uint8 *Row = ((uint8 *)Layer->Raster.OriginalBuffer + Layer->Raster.Pitch*Paint.Min.y); + for(int Y = Paint.Min.y; + Y < Paint.Max.y; + ++Y) + { + uint32 *Pixel = (uint32 *)(Row + Paint.Min.x*sizeof(uint32)); + for(int X = Paint.Min.x; + X < Paint.Max.x; + ++X) + { + if (X < Layer->Raster.Width && X > 0) { + v2 Pos = V2(abs(Center.x - (X - Paint.Min.x)), abs(Center.y - (Y - Paint.Min.y))); + real32 L = sqrt(LengthSq(Pos)) + Center.x; + real32 Gradient = pow(Ceil(L, MaxLength) / MaxLength, Brush.Hardness); + RenderAlpha(Pixel, ColToUint32({1.0f, 1.0f, 1.0f, 1.0f - Gradient})); + } + Pixel++; + } + Row += Layer->Raster.Pitch; + } +} +*/ diff --git a/src/prenderer.cpp b/src/prenderer.cpp new file mode 100644 index 0000000..54b19cf --- /dev/null +++ b/src/prenderer.cpp @@ -0,0 +1,1914 @@ +static v2 +T_CompUVToLayerUV(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, v2 CompUV) +{ + real32 X = CompUV.x*FileWidth; + real32 Y = CompUV.y*FileHeight; + + real32 Rad = (T.rotation* (PI / 180)); + v2 XAxis = (SourceWidth * T.scale)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (SourceHeight * -T.scale)*V2(sin(Rad), -cos(Rad)); + + v2 Pos = {T.x, T.y}; + v2 Origin = Pos - (XAxis * T.ax) - (YAxis * T.ay); + + v2 XAxisPerp = (1.0f / LengthSq(XAxis))*XAxis; + v2 YAxisPerp = (1.0f / LengthSq(YAxis))*YAxis; + + real32 StartVectorX = X - Origin.x; + real32 StartVectorY = Y - Origin.y; + real32 LayerU = (StartVectorX * XAxisPerp.x) + (StartVectorY * XAxisPerp.y); + real32 LayerV = (StartVectorX * YAxisPerp.x) + (StartVectorY * YAxisPerp.y); + return V2(LayerU, LayerV); +} + +static v2 +T_CompPosToLayerPos(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, v2 CompUV) +{ + v2 UV = T_CompUVToLayerUV(T, FileWidth, FileHeight, SourceWidth, SourceHeight, CompUV/V2(FileWidth, FileHeight)); + return UV*V2(SourceWidth, SourceHeight); +} + +static v2 +Transform_ScreenSpaceToLocal(layer_transforms T, uint32 FileWidth, uint32 FileHeight, uint32 SourceWidth, uint32 SourceHeight, + ImVec2 CompPos, ImVec2 CompZoom, ImVec2 ViewportMin, ImVec2 Point) +{ + v2 CompUV = ImGui_ScreenPointToCompUV(ViewportMin, CompPos, CompZoom, Point); + v2 LayerUV = T_CompUVToLayerUV(T, FileWidth, FileHeight, SourceWidth, SourceHeight, CompUV); + return V2(LayerUV.x * SourceWidth, LayerUV.y * SourceHeight); +} + +static void +Layer_GetDimensions(memory *Memory, block_layer *Layer, int *Width, int *Height) +{ + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + *Width = Source->Width; + *Height = Source->Height; + } else { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + *Width = Comp->Width; + *Height = Comp->Height; + } +} + +// Transform given data based on state's Interact data. +static void +Transform_ApplyInteractive(interact_transform Interact, real32 *OutputX, real32 *OutputY, real32 *OutputRotation, real32 *OutputScale) +{ + v2 BoxLength = Interact.Max - Interact.Min; + v2 Center = Interact.Max - (BoxLength/2); + + real32 Point0X = Center.x - *OutputX; + real32 Point0Y = Center.y - *OutputY; + + real32 Rad = Interact.Radians; + real32 Rotation = Interact.Radians / (PI / 180); + + v2 XAxis = (Point0X * Interact.Scale)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (Point0Y * -Interact.Scale)*V2(sin(Rad), -cos(Rad)); + + real32 X0 = -XAxis.x - YAxis.x + Center.x; + real32 Y0 = -XAxis.y - YAxis.y + Center.y; + + *OutputX = X0 + Interact.Position.x; + *OutputY = Y0 + Interact.Position.y; + *OutputRotation += Rotation; + *OutputScale += Interact.Scale - 1.0f; +} + +static void +Transform_IterateOuterBounds(block_layer *Layer, uint32 Width, uint32 Height, real32 *MinX, real32 *MinY, real32 *MaxX, real32 *MaxY) +{ + real32 Rad = (Layer->rotation.CurrentValue * (PI / 180)); + real32 s = Layer->scale.CurrentValue; + + v2 XAxis = (Width * s)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (Height * -s)*V2(sin(Rad), -cos(Rad)); + + real32 AnchorX = Layer->ax.CurrentValue; + real32 AnchorY = Layer->ay.CurrentValue; + + v2 Pos = {Layer->x.CurrentValue, Layer->y.CurrentValue}; + v2 Origin = Pos - (XAxis * AnchorX) - (YAxis * AnchorY); + + real32 XLengthSq = 1.0f / LengthSq(XAxis); + real32 YLengthSq = 1.0f / LengthSq(YAxis); + + v2 Points[4] = {Origin, Origin + XAxis, Origin + YAxis, Origin + XAxis + YAxis}; + for (int i = 0; i < 4; i++) { + if (Points[i].x < *MinX) { *MinX = Points[i].x; } + if (Points[i].y < *MinY) { *MinY = Points[i].y; } + if (Points[i].x > *MaxX) { *MaxX = Points[i].x; } + if (Points[i].y > *MaxY) { *MaxY = Points[i].y; } + } +} +static void +Transform_Recurse(project_state *State, memory *Memory, block_composition *MainComp, uint32 CompIndex, block_layer *ParentLayer[4], uint32 Recursions, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, + real32 *MinX, real32 *MinY, real32 *MaxX, real32 *MaxY) +{ + sorted_comp_array *SortedCompStart = &SortedCompArray[CompIndex]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); + for (int i = 0; i < SortedCompStart->LayerCount; i++) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsPrecomp) { + ParentLayer[Recursions] = Layer; + Transform_Recurse(State, Memory, MainComp, Layer->Block_Source_Index, ParentLayer, Recursions + 1, SortedCompArray, SortedLayerArray, + MinX, MinY, MaxX, MaxY); + } + if (Layer->IsSelected) { + uint32 Width = 0, Height = 0; + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + Width = Source->Width; + Height = Source->Height; + } else { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + Width = Comp->Width; + Height = Comp->Height; + } + + v2 Point[5] = { V2(Width*Layer->ax.CurrentValue, Height*Layer->ay.CurrentValue), V2(0, 0), V2(Width, 0), V2(0, Height), V2(Width, Height) }; + + layer_transforms T = Layer_GetTransforms(Layer); + + v2 NewPos[5]; + for (int i = 0; i < 5; i++) { + NewPos[i] = TransformPoint(T, Width, Height, Point[i]); + } + + int i = 0; + while (i < Recursions) { + T = Layer_GetTransforms(ParentLayer[i]); + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, ParentLayer[i]->Block_Source_Index); + Width = Comp->Width; + Height = Comp->Height; + for (int i = 0; i < 5; i++) { + NewPos[i] = TransformPoint(T, Width, Height, NewPos[i]); + } + i++; + } + + for (int i = 0; i < 4; i++) { + if (NewPos[i+1].x < *MinX) { *MinX = NewPos[i+1].x; } + if (NewPos[i+1].y < *MinY) { *MinY = NewPos[i+1].y; } + if (NewPos[i+1].x > *MaxX) { *MaxX = NewPos[i+1].x; } + if (NewPos[i+1].y > *MaxY) { *MaxY = NewPos[i+1].y; } + } + } + } +} + +// IMPORTANT(fox): The selection state and ordering of layers cannot change +// until this action is exited/committed! +static void +Interact_Transform_Begin(project_data *File, memory *Memory, project_state *State, ImVec2 OGPos, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) +{ + real32 MinX = 100000; + real32 MinY = 100000; + real32 MaxX = -100000; + real32 MaxY = -100000; + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + block_layer *ParentLayer[4]; + Transform_Recurse(State, Memory, MainComp, File->PrincipalCompIndex, ParentLayer, 0, + SortedCompArray, SortedLayerArray, + &MinX, &MinY, &MaxX, &MaxY); + if (MinX != 100000) { + State->Interact_Active = interact_type_viewport_transform; + interact_transform *Interact = (interact_transform *)&State->Interact_Offset[0]; + Interact->Min = V2(MinX, MinY); + Interact->Max = V2(MaxX, MaxY); + Interact->Position = V2(0); + Interact->Radians = 0; + Interact->Scale = 1.0f; + Interact->OGPos = OGPos; + } + /* + bool32 Activate = false; + // Find the max dimensions of all the selected layers. + for (int i = 0; i < File->Layer_Count; i++) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (!Layer->IsSelected) + continue; + uint32 Width = 0, Height = 0; + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + Width = Source->Width; + Height = Source->Height; + } else { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + Width = Comp->Width; + Height = Comp->Height; + } + Transform_IterateOuterBounds(Layer, Width, Height, &MinX, &MinY, &MaxX, &MaxY); + Activate = true; + } + if (Activate) { + State->Interact_Active = interact_type_viewport_transform; + interact_transform *Interact = (interact_transform *)&State->Interact_Offset[0]; + Interact->Min = V2(MinX, MinY); + Interact->Max = V2(MaxX, MaxY); + Interact->Position = V2(0); + Interact->Radians = 0; + Interact->Scale = 1.0f; + Interact->OGPos = OGPos; + } + */ +} + +static v2 +TransformPoint(layer_transforms T, real32 Width, real32 Height, v2 Point) +{ + real32 Rad = (T.rotation * (PI / 180)); + v2 XAxis = (Point.x - T.ax*Width) * T.scale * V2(cos(Rad), sin(Rad)); + v2 YAxis = (Point.y - T.ay*Height) * -T.scale * V2(sin(Rad), -cos(Rad)); + v2 LocalPoint = XAxis + YAxis; + return V2(T.x + LocalPoint.x, T.y + LocalPoint.y); +} + + +static ImVec2 +Layer_LocalToScreenSpace(project_state *State, memory *Memory, block_layer *Layer, ui *UI, uint32 PrincipalCompIndex, v2 Point) +{ + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, PrincipalCompIndex); + + uint32 Width = 0, Height = 0; + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + Width = Source->Width; + Height = Source->Height; + } else { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + Width = Comp->Width; + Height = Comp->Height; + } + + layer_transforms T = Layer_GetTransforms(Layer); + + if (State->Interact_Active == interact_type_viewport_transform && Layer->IsSelected == 1) { + Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); + } + + v2 NewPos = TransformPoint(T, Width, Height, Point); + + if (Layer->Block_Composition_Index != PrincipalCompIndex) { + layer_transforms T = Layer_GetTransforms(Layer); + NewPos = TransformPoint(T, Width, Height, NewPos); + } + + v2 CompUV = NewPos / V2(MainComp->Width, MainComp->Height); + + v2 ScreenPoint = V2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, + UI->CompPos.y + CompUV.y * UI->CompZoom.y); + + return ImVec2(ScreenPoint.x, ScreenPoint.y); +} + +static void +Fallback_RenderLayer(transform_info T, void *OutputBuffer, rectangle RenderRegion); +static void +Fallback_RenderDirect(direct_info T, void *OutputBuffer, rectangle RenderRegion); + +static void +RenderLayers(render_entry Entry) { + switch (Entry.RenderType) + { + case render_type_main: + { + Fallback_RenderLayer(*(transform_info *)Entry.RenderData, Entry.OutputBuffer, Entry.RenderRegion); + } break; + case render_type_notransform: + { + Fallback_RenderDirect(*(direct_info *)Entry.RenderData, Entry.OutputBuffer, Entry.RenderRegion); + } break; + case render_type_notransform_swap: + { + Fallback_RenderDirect(*(direct_info *)Entry.RenderData, Entry.OutputBuffer, Entry.RenderRegion); + } break; + case render_type_brush: + { + PaintTest(*(brush_info *)Entry.RenderData, Entry.OutputBuffer, Entry.RenderRegion); + } break; + default: + { + Assert(0); + } + } +#if 0 +#if ARM + Fallback_RenderLayer(RenderData->TransformInfo[i], RenderInfo->CompBuffer, RenderRegion); +#else + if (InstructionMode == instruction_mode_avx) + AVX2_RenderLayer(Entry.T, Entry.OutputBuffer, Entry.RenderRegion); + else + Fallback_RenderLayer(Entry.T, Entry.OutputBuffer, Entry.RenderRegion); +#endif +#endif +} + +static void +Renderer_Start(void *Data, void *OutputBuffer, render_type RenderType, rectangle RenderRegion) +{ +#if DEBUG + if (Debug.NoThreading) { + render_entry Entry = { Data, OutputBuffer, RenderType, RenderRegion }; + RenderLayers(Entry); + return; + } +#endif + // CPU + Threading_BitmapOp(Data, OutputBuffer, RenderType, RenderRegion); +} + +static void +Renderer_Check(bool32 *Test, render_type RenderType) +{ +#if DEBUG + if (Debug.NoThreading) { + *Test = true; + return; + } +#endif + // CPU + *Test = Threading_IsActive(RenderType); +} + + +static transform_info +Transform_Calculate(project_state *State, memory *Memory, project_data *File, block_layer *Layer, block_composition *Comp, + int Width, int Height, int BytesPerPixel) +{ + transform_info TransformInfo; + + real32 Rotation = Layer->rotation.CurrentValue; + real32 X = Layer->x.CurrentValue; + real32 Y = Layer->y.CurrentValue; + real32 s = Layer->scale.CurrentValue; + blend_mode BlendMode = Layer->BlendMode; + + if (State->Interact_Active == interact_type_viewport_transform && Layer->IsSelected == 1) { + Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &X, &Y, &Rotation, &s); + } + + /* + state_file_ui *UI = &State->Context[State->CurrentFileIndex].UI; + if (UI->IsInteracting == true && UI->InteractMode == interact_transforms && Layer->IsSelected && !Layer->IsAdjustment) + Transform_ApplyInteractive(UI, &X, &Y, &Rotation, &s); + + if (UI->IsInteractingBlendmode == true && Layer->IsSelected) + BlendMode = UI->InteractBlendmode; + */ + + real32 Rad = (Rotation * (PI / 180)); + // v2 Scale = {Source->Raster.Width * s, Source->Raster.Height * s}; + + v2 XAxis = (Width * s)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (Height * -s)*V2(sin(Rad), -cos(Rad)); + + real32 AnchorX = Layer->ax.CurrentValue; + real32 AnchorY = Layer->ay.CurrentValue; + + v2 Pos = {X, Y}; + v2 Origin = Pos - (XAxis * AnchorX) - (YAxis * AnchorY); + + real32 XLengthSq = 1.0f / LengthSq(XAxis); + real32 YLengthSq = 1.0f / LengthSq(YAxis); + + int32 MaxX = 0; + int32 MaxY = 0; + int32 MinX = Comp->Width; + int32 MinY = Comp->Height; + + v2 Points[4] = {Origin, Origin + XAxis, Origin + YAxis, Origin + XAxis + YAxis}; + for (int i = 0; i < 4; i++) { + if (Points[i].x < MinX) { MinX = Points[i].x; } + if (Points[i].y < MinY) { MinY = Points[i].y; } + if (Points[i].x > MaxX) { MaxX = Points[i].x; } + if (Points[i].y > MaxY) { MaxY = Points[i].y; } + } + TransformInfo.XAxisPX = XLengthSq*XAxis.x; + TransformInfo.XAxisPY = XLengthSq*XAxis.y; + TransformInfo.YAxisPX = YLengthSq*YAxis.x; + TransformInfo.YAxisPY = YLengthSq*YAxis.y; + + TransformInfo.BufferWidth = Comp->Width; + TransformInfo.BufferHeight = Comp->Height; + TransformInfo.BufferBytesPerPixel = Comp->BytesPerPixel; + TransformInfo.BufferBits = Bitmap_ByteInfo(Comp->BytesPerPixel); + + TransformInfo.LayerWidth = Width; + TransformInfo.LayerHeight = Height; + TransformInfo.LayerBytesPerPixel = BytesPerPixel; + TransformInfo.LayerBits = Bitmap_ByteInfo(BytesPerPixel); + + TransformInfo.LayerOpacity = Layer->opacity.CurrentValue; + TransformInfo.BlendMode = BlendMode; + TransformInfo.OriginX = Origin.x; + TransformInfo.OriginY = Origin.y; + TransformInfo.BufferPitch = Comp->Width*Comp->BytesPerPixel; + TransformInfo.LayerPitch = Width*BytesPerPixel; + TransformInfo.ClipRect = {MinX, MinY, MaxX, MaxY}; + + TransformInfo.IsAdjustment = Layer->IsAdjustment; + + return TransformInfo; +} + +// NOTE(fox): is this too ridiculous? i don't trust inline +#define Fallback_Blend() \ + switch (T.BlendMode)\ + {\ + case blend_normal:\ + {\ + } break;\ + case blend_multiply:\ + {\ + R_Blend = R_Dest * R_Col;\ + G_Blend = G_Dest * G_Col;\ + B_Blend = B_Dest * B_Col;\ + } break;\ + case blend_colorburn:\ + {\ + /* NOTE(fox): Padding to prevent actual crashing from zero division */ \ + R_Blend = 1.0f - ((1.0f - R_Dest) / (R_Col + 0.001f));\ + G_Blend = 1.0f - ((1.0f - G_Dest) / (G_Col + 0.001f));\ + B_Blend = 1.0f - ((1.0f - B_Dest) / (B_Col + 0.001f));\ + } break;\ + case blend_linearburn:\ + {\ + R_Blend = (R_Dest + R_Col) - 1.0f;\ + G_Blend = (G_Dest + G_Col) - 1.0f;\ + B_Blend = (B_Dest + B_Col) - 1.0f;\ + } break;\ + case blend_add:\ + {\ + R_Blend = R_Dest + R_Col;\ + G_Blend = G_Dest + G_Col;\ + B_Blend = B_Dest + B_Col;\ + } break;\ + case blend_screen:\ + {\ + R_Blend = 1.0f - ((1.0f - R_Dest) * (1.0f - R_Col));\ + G_Blend = 1.0f - ((1.0f - G_Dest) * (1.0f - G_Col));\ + B_Blend = 1.0f - ((1.0f - B_Dest) * (1.0f - B_Col));\ + } break;\ + case blend_overlay:\ + {\ + if (R_Dest < 0.5) {\ + R_Blend = 2.0f * R_Dest * R_Col;\ + } else {\ + R_Blend = 1.0f - (2.0f * (1.0f - R_Dest) * (1.0f - R_Col));\ + }\ + if (G_Dest < 0.5) {\ + G_Blend = 2.0f * G_Dest * G_Col;\ + } else {\ + G_Blend = 1.0f - (2.0f * (1.0f - G_Dest) * (1.0f - G_Col));\ + }\ + if (B_Dest < 0.5) {\ + B_Blend = 2.0f * B_Dest * B_Col;\ + } else {\ + B_Blend = 1.0f - (2.0f * (1.0f - B_Dest) * (1.0f - B_Col));\ + }\ + } break;\ + case blend_softlight:\ + {\ + /* using Pegtop's equation */ \ + R_Blend = ((1.0f - R_Col * 2) * R_Dest * R_Dest) + (R_Col * 2 * R_Dest);\ + G_Blend = ((1.0f - G_Col * 2) * G_Dest * G_Dest) + (G_Col * 2 * G_Dest);\ + B_Blend = ((1.0f - B_Col * 2) * B_Dest * B_Dest) + (B_Col * 2 * B_Dest);\ + } break;\ + case blend_hardlight:\ + {\ + if (R_Dest > 0.5) {\ + R_Blend = 2.0f * R_Dest * R_Col;\ + } else {\ + R_Blend = 1.0f - (2.0f * (1.0f - R_Dest) * (1.0f - R_Col));\ + }\ + if (G_Dest > 0.5) {\ + G_Blend = 2.0f * G_Dest * G_Col;\ + } else {\ + G_Blend = 1.0f - (2.0f * (1.0f - G_Dest) * (1.0f - G_Col));\ + }\ + if (B_Dest > 0.5) {\ + B_Blend = 2.0f * B_Dest * B_Col;\ + } else {\ + B_Blend = 1.0f - (2.0f * (1.0f - B_Dest) * (1.0f - B_Col));\ + }\ + } break;\ + case blend_subtract:\ + {\ + R_Blend = R_Dest - R_Col;\ + G_Blend = G_Dest - G_Col;\ + B_Blend = B_Dest - B_Col;\ + } break;\ + case blend_divide:\ + {\ + R_Blend = R_Dest / (R_Col + 0.001f);\ + G_Blend = G_Dest / (G_Col + 0.001f);\ + B_Blend = B_Dest / (B_Col + 0.001f);\ + } break;\ + case blend_difference:\ + {\ + if (R_Col - R_Dest > 0) {\ + R_Blend = R_Col - R_Dest;\ + } else {\ + R_Blend = R_Dest - R_Col;\ + }\ + if (G_Col - G_Dest > 0) {\ + G_Blend = G_Col - G_Dest;\ + } else {\ + G_Blend = G_Dest - G_Col;\ + }\ + if (B_Col - B_Dest > 0) {\ + B_Blend = B_Col - B_Dest;\ + } else {\ + B_Blend = B_Dest - B_Col;\ + }\ + } break;\ + }\ + +static void +Fallback_RenderDirect(direct_info T, void *OutputBuffer, rectangle RenderRegion) +{ + rectangle LayerBounds = ClipRectangle( T.ClipRect, RenderRegion); + + Assert(LayerBounds.Max.x <= T.BufferWidth); + Assert(LayerBounds.Max.y <= T.BufferHeight); + + for (int16 Y = LayerBounds.Min.y; Y < LayerBounds.Max.y; Y++) + { + for (int16 X = LayerBounds.Min.x; X < LayerBounds.Max.x; X++) + { + uint16 LX = X; + uint16 LY = Y; + uint16 LXPlus = Ceil(X+1, (uint32)T.BufferWidth - 1); + uint16 LYPlus = Ceil(Y+1, (uint32)T.BufferHeight - 1); + + uint8 *TexPTR0 = ((uint8 *)T.SourceBuffer + ((uint16)T.BufferPitch * LY) + (LX * (uint16)T.BufferBytesPerPixel)); + + uint32 *R_SrcAddress = (uint32 *)(TexPTR0 + T.BufferBits.ByteOffset * 0); + uint32 *G_SrcAddress = (uint32 *)(TexPTR0 + T.BufferBits.ByteOffset * 1); + uint32 *B_SrcAddress = (uint32 *)(TexPTR0 + T.BufferBits.ByteOffset * 2); + uint32 *A_SrcAddress = (uint32 *)(TexPTR0 + T.BufferBits.ByteOffset * 3); + + real32 R_Src = (real32)(*R_SrcAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + + real32 R_Col = (real32)(*R_SrcAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 G_Col = (real32)(*G_SrcAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 B_Col = (real32)(*B_SrcAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 A_Col = (real32)(*A_SrcAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + + if (A_Col == 0) + continue; + + real32 LayerAlpha = A_Col * T.Opacity; + + uint8 *DestPixel =((uint8 *)OutputBuffer + ((uint16)Y * (uint16)T.BufferPitch) + ((uint16)X * (uint16)T.BufferBytesPerPixel)); + + uint32 *R_DestAddress = (uint32 *)(DestPixel + T.BufferBits.ByteOffset * 0); + uint32 *G_DestAddress = (uint32 *)(DestPixel + T.BufferBits.ByteOffset * 1); + uint32 *B_DestAddress = (uint32 *)(DestPixel + T.BufferBits.ByteOffset * 2); + uint32 *A_DestAddress = (uint32 *)(DestPixel + T.BufferBits.ByteOffset * 3); + + uint32 R_DestInt = (*R_DestAddress & T.BufferBits.MaskPixel); + uint32 G_DestInt = (*G_DestAddress & T.BufferBits.MaskPixel); + uint32 B_DestInt = (*B_DestAddress & T.BufferBits.MaskPixel); + uint32 A_DestInt = (*A_DestAddress & T.BufferBits.MaskPixel); + + real32 R_Dest = (real32)(R_DestInt) * T.BufferBits.Normalized; + real32 G_Dest = (real32)(G_DestInt) * T.BufferBits.Normalized; + real32 B_Dest = (real32)(B_DestInt) * T.BufferBits.Normalized; + real32 A_Dest = (real32)(A_DestInt) * T.BufferBits.Normalized; + real32 Test = (A_Dest > 0.01) ? 1 : 0; + + real32 R_Blend = R_Col; + real32 G_Blend = G_Col; + real32 B_Blend = B_Col; + real32 A_Blend = A_Col; + // A_Blend = (A_Blend >= 0.04045) ? pow((A_Blend + 0.055) / (1 + 0.055), 2.4) : A_Blend / 12.92; + + if (LayerAlpha != 1.0f || T.BlendMode != blend_normal) { + + Fallback_Blend(); + + if (A_Dest == 0) { + A_Blend = LayerAlpha; + } else { + A_Blend = A_Dest + ((1.0f - A_Dest) * LayerAlpha); + real32 Alpha = pow(LayerAlpha, A_Dest); + R_Blend = (R_Dest * (1.0f - Alpha)) + (R_Blend * Alpha); + G_Blend = (G_Dest * (1.0f - Alpha)) + (G_Blend * Alpha); + B_Blend = (B_Dest * (1.0f - Alpha)) + (B_Blend * Alpha); + } + } + + uint32 R_Out = (uint32)(Normalize(R_Blend) * T.BufferBits.Bits); + uint32 G_Out = (uint32)(Normalize(G_Blend) * T.BufferBits.Bits); + uint32 B_Out = (uint32)(Normalize(B_Blend) * T.BufferBits.Bits); + uint32 A_Out = (uint32)(Normalize(A_Blend) * T.BufferBits.Bits); + + if (T.SwapActive) + { + *R_SrcAddress = (*R_SrcAddress & ~T.BufferBits.MaskPixel) | R_DestInt; + *G_SrcAddress = (*G_SrcAddress & ~T.BufferBits.MaskPixel) | G_DestInt; + *B_SrcAddress = (*B_SrcAddress & ~T.BufferBits.MaskPixel) | B_DestInt; + *A_SrcAddress = (*A_SrcAddress & ~T.BufferBits.MaskPixel) | A_DestInt; + } + *R_DestAddress = (*R_DestAddress & ~T.BufferBits.MaskPixel) | R_Out; + *G_DestAddress = (*G_DestAddress & ~T.BufferBits.MaskPixel) | G_Out; + *B_DestAddress = (*B_DestAddress & ~T.BufferBits.MaskPixel) | B_Out; + *A_DestAddress = (*A_DestAddress & ~T.BufferBits.MaskPixel) | A_Out; + } + } +} + +static void +Fallback_RenderLayer(transform_info T, void *OutputBuffer, rectangle RenderRegion) +{ + rectangle LayerBounds = ClipRectangle( T.ClipRect, RenderRegion); + + Assert(LayerBounds.Max.x <= T.BufferWidth); + Assert(LayerBounds.Max.y <= T.BufferHeight); + + for (int Y = LayerBounds.Min.y; Y < LayerBounds.Max.y; Y++) + { + real32 StartVectorY = (real32)Y - T.OriginY; + + for (int X = LayerBounds.Min.x; X < LayerBounds.Max.x; X++) + { + real32 StartVectorX = X - T.OriginX; + real32 U = (StartVectorX * T.XAxisPX) + (StartVectorY * T.XAxisPY); + real32 V = (StartVectorX * T.YAxisPX) + (StartVectorY * T.YAxisPY); + + if (U < 1.0f && U >= 0.0f && V < 1.0f && V >= 0.0f) { + + real32 TexXFull = U * T.LayerWidth; + uint32 TexXInt = (uint32)TexXFull; + real32 TexX = TexXFull - TexXInt; + + real32 TexYFull = V * T.LayerHeight; + uint32 TexYInt = (uint32)TexYFull; + real32 TexY = TexYFull - TexYInt; + + real32 TexXInv = 1 - TexX; + real32 TexYInv = 1 - TexY; + real32 TexBothXInv = TexXInv * TexY; + real32 TexBothYInv = TexX * TexYInv; + real32 TexBoth = TexY * TexX; + real32 TexBothInv = TexXInv * TexYInv; + + uint32 XLookup, YLookup, PixelToSeek; + + uint32 LX = TexXInt; + uint32 LY = TexYInt; + uint32 LXPlus = Ceil(TexXInt+1, (uint32)T.LayerWidth - 1); + uint32 LYPlus = Ceil(TexYInt+1, (uint32)T.LayerHeight - 1); + + uint8 *TexPTR0 = ((uint8 *)T.SourceBuffer + ((uint32)T.LayerPitch * LY) + (LX * (uint32)T.LayerBytesPerPixel)); + uint8 *TexPTR1 = ((uint8 *)T.SourceBuffer + ((uint32)T.LayerPitch * LY) + (LXPlus * (uint32)T.LayerBytesPerPixel)); + uint8 *TexPTR2 = ((uint8 *)T.SourceBuffer + ((uint32)T.LayerPitch * LYPlus) + (LX * (uint32)T.LayerBytesPerPixel)); + uint8 *TexPTR3 = ((uint8 *)T.SourceBuffer + ((uint32)T.LayerPitch * LYPlus) + (LXPlus * (uint32)T.LayerBytesPerPixel)); + + uint32 PixelA = *(uint32 *)TexPTR0; + uint32 PixelB = *(uint32 *)TexPTR1; + uint32 PixelC = *(uint32 *)TexPTR2; + uint32 PixelD = *(uint32 *)TexPTR3; + + +#if 0 + real32 TexRA = (real32)(PixelA & 0xFF) * Normalized255; + real32 TexRB = (real32)(PixelB & 0xFF) * Normalized255; + real32 TexRC = (real32)(PixelC & 0xFF) * Normalized255; + real32 TexRD = (real32)(PixelD & 0xFF) * Normalized255; + + real32 TexGA = (real32)((PixelA >> 8) & 0xFF) * Normalized255; + real32 TexGB = (real32)((PixelB >> 8) & 0xFF) * Normalized255; + real32 TexGC = (real32)((PixelC >> 8) & 0xFF) * Normalized255; + real32 TexGD = (real32)((PixelD >> 8) & 0xFF) * Normalized255; + + real32 TexBA = (real32)((PixelA >> 16) & 0xFF) * Normalized255; + real32 TexBB = (real32)((PixelB >> 16) & 0xFF) * Normalized255; + real32 TexBC = (real32)((PixelC >> 16) & 0xFF) * Normalized255; + real32 TexBD = (real32)((PixelD >> 16) & 0xFF) * Normalized255; + + real32 TexAA = (real32)((PixelA >> 24) & 0xFF) * Normalized255; + real32 TexAB = (real32)((PixelB >> 24) & 0xFF) * Normalized255; + real32 TexAC = (real32)((PixelC >> 24) & 0xFF) * Normalized255; + real32 TexAD = (real32)((PixelD >> 24) & 0xFF) * Normalized255; +#else + real32 TexRA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAA = (real32)(*(uint32 *)(TexPTR0 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAB = (real32)(*(uint32 *)(TexPTR1 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAC = (real32)(*(uint32 *)(TexPTR2 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + + real32 TexRD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 0) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexGD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 1) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexBD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 2) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + real32 TexAD = (real32)(*(uint32 *)(TexPTR3 + T.LayerBits.ByteOffset * 3) & T.LayerBits.MaskPixel) * T.LayerBits.Normalized; + +#endif + + real32 R_Col = (TexBothInv * TexRA) + (TexBothYInv * TexRB) + + (TexBothXInv * TexRC) + (TexBoth * TexRD); + real32 G_Col = (TexBothInv * TexGA) + (TexBothYInv * TexGB) + + (TexBothXInv * TexGC) + (TexBoth * TexGD); + real32 B_Col = (TexBothInv * TexBA) + (TexBothYInv * TexBB) + + (TexBothXInv * TexBC) + (TexBoth * TexBD); + real32 A_Col = (TexBothInv * TexAA) + (TexBothYInv * TexAB) + + (TexBothXInv * TexAC) + (TexBoth * TexAD); + + real32 LayerAlpha = A_Col * T.LayerOpacity; + +#if DEBUG + if (Debug.DisableAlpha == 1) { + A_Col = 1; + LayerAlpha = 1; + } +#endif + + real32 R_Blend = R_Col; + real32 G_Blend = G_Col; + real32 B_Blend = B_Col; + real32 A_Blend = A_Col; + + uint8 *DestPixel =((uint8 *)OutputBuffer + ((uint32)Y * (uint32)T.BufferPitch) + ((uint32)X * (uint32)T.BufferBytesPerPixel)); + Assert(X != (T.BufferWidth)); + + uint8 *R_DestAddress = (DestPixel + T.BufferBits.ByteOffset * 0); + uint8 *G_DestAddress = (DestPixel + T.BufferBits.ByteOffset * 1); + uint8 *B_DestAddress = (DestPixel + T.BufferBits.ByteOffset * 2); + uint8 *A_DestAddress = (DestPixel + T.BufferBits.ByteOffset * 3); + + if (LayerAlpha != 1.0f || T.BlendMode != blend_normal) { + + real32 R_Dest = (real32)(*R_DestAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 G_Dest = (real32)(*G_DestAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 B_Dest = (real32)(*B_DestAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + real32 A_Dest = (real32)(*A_DestAddress & T.BufferBits.MaskPixel) * T.BufferBits.Normalized; + + Fallback_Blend(); + + R_Blend = (R_Dest * (1.0f - LayerAlpha)) + (R_Blend * LayerAlpha); + G_Blend = (G_Dest * (1.0f - LayerAlpha)) + (G_Blend * LayerAlpha); + B_Blend = (B_Dest * (1.0f - LayerAlpha)) + (B_Blend * LayerAlpha); + + if (T.BlendMode == blend_normal) + A_Blend = A_Dest + LayerAlpha; + // A_Blend = A_Dest + ((1.0f - A_Dest) * LayerAlpha); + else + A_Blend = A_Dest; +#if DEBUG + if (Debug.DisableAlpha == 1) { + G_Blend = R_Blend; + B_Blend = R_Blend; + } else + if (Debug.DisableAlpha == 2) { + R_Blend = LayerAlpha; + G_Blend = LayerAlpha; + B_Blend = LayerAlpha; + } +#endif + } + + uint8 R_Out = (uint8)(Normalize(R_Blend) * T.BufferBits.Bits); + uint8 G_Out = (uint8)(Normalize(G_Blend) * T.BufferBits.Bits); + uint8 B_Out = (uint8)(Normalize(B_Blend) * T.BufferBits.Bits); + uint8 A_Out = (uint8)(Normalize(A_Blend) * T.BufferBits.Bits); + + *R_DestAddress = R_Out; + *G_DestAddress = G_Out; + *B_DestAddress = B_Out; + *A_DestAddress = A_Out; + // *R_DestAddress = 255; + // *G_DestAddress = 255; + // *B_DestAddress = 255; + // *A_DestAddress = 255; + } + } + } +} + +#if 0 +static void +Layer_CalcRotatedOffset(project_layer *Layer, v2 Increment, v2 Divisor, real32 *ValueX, real32 *ValueY) +{ + + real32 Rad = (Layer->rotation.CurrentValue.f * (PI / 180)); + real32 s = Layer->scale.CurrentValue.f; + + v2 XAxis = V2(cos(Rad), sin(Rad)) * (Increment.x / s); + v2 YAxis = V2(sin(Rad), -cos(Rad)) * (Increment.y / -s); + + *ValueX += XAxis.x/Divisor.x; + *ValueY -= XAxis.y/Divisor.y; + *ValueX -= YAxis.x/Divisor.x; + *ValueY += YAxis.y/Divisor.y; +} + +static transform_info +CalculateTransforms(project_layer *Layer, comp_buffer *CompBuffer) +{ + transform_info TransformInfo; + source *Source = Layer->Source; + + real32 Rad = (Layer->rotation.CurrentValue.f * (PI / 180)); + real32 s = Layer->scale.CurrentValue.f; + // v2 Scale = {Source->Raster.Width * s, Source->Raster.Height * s}; + + v2 XAxis = (Source->Info.Width * s)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (Source->Info.Height * -s)*V2(sin(Rad), -cos(Rad)); + + real32 AnchorX = Layer->ax.CurrentValue.f; + real32 AnchorY = Layer->ay.CurrentValue.f; + + v2 Pos = {Layer->x.CurrentValue.f, Layer->y.CurrentValue.f}; + v2 Origin = Pos - (XAxis * AnchorX) - (YAxis * AnchorY); + + real32 XLengthSq = 1.0f / LengthSq(XAxis); + real32 YLengthSq = 1.0f / LengthSq(YAxis); + + int32 MaxX = 0; + int32 MaxY = 0; + int32 MinX = CompBuffer->Width; + int32 MinY = CompBuffer->Height; + + v2 Points[4] = {Origin, Origin + XAxis, Origin + YAxis, Origin + XAxis + YAxis}; + for (int i = 0; i < 4; i++) { + if (Points[i].x < MinX) { MinX = Points[i].x; } + if (Points[i].y < MinY) { MinY = Points[i].y; } + if (Points[i].x > MaxX) { MaxX = Points[i].x; } + if (Points[i].y > MaxY) { MaxY = Points[i].y; } + } + + TransformInfo.XAxisPX = XLengthSq*XAxis.x; + TransformInfo.XAxisPY = XLengthSq*XAxis.y; + TransformInfo.YAxisPX = YLengthSq*YAxis.x; + TransformInfo.YAxisPY = YLengthSq*YAxis.y; + + uint16 Width = Source->Info.Width; + uint16 Height = Source->Info.Height; + uint16 WidthP, HeightP; + Bitmap_CalcPackedDimensions(Width, Height, &WidthP, &HeightP); + + TransformInfo.LayerWidth = Width; + TransformInfo.LayerHeight = Height; + TransformInfo.FullLayerWidth = WidthP; + TransformInfo.FullLayerHeight = HeightP; + TransformInfo.LayerOpacity = Layer->opacity.CurrentValue.f; + TransformInfo.BlendMode =Layer->BlendMode; + TransformInfo.OriginX = Origin.x; + TransformInfo.OriginY = Origin.y; + TransformInfo.BufferPitch = CompBuffer->Width*CompBuffer->BytesPerPixel; + TransformInfo.LayerPitch = Source->Info.Width*Source->Info.BytesPerPixel; + TransformInfo.ClipRect = {MinX - (MinX & 3), MinY, MaxX + 1, MaxY + 1}; + + TransformInfo.SourceBuffer = Layer->BitmapInfo.BitmapBuffer; + + return TransformInfo; +} + +static void +EndRenderState(project_state *State) +{ + IsRendering = false; + + for (int16 i = 0; i < State->NumberOfLayersToRender; i++) + { + State->LayersToRender[i] = 0; + } + + State->NumberOfLayersToRender = 0; + SDL_AtomicSet(&CurrentEntry, 0); + SDL_AtomicSet(&QueuedEntries, 0); + SDL_AtomicSet(&CompletedEntries, 0); + +} + +static void +RenderLayers(render_queue *RenderInfo, rectangle RenderRegion) { + for (int16 i = 0; i < RenderInfo->State->NumberOfLayersToRender; i++) { + int16 Idx = RenderInfo->State->LayersToRender[i]; + +#if ARM + if (InstructionMode == instruction_mode_neon) + Fallback_RenderLayer(RenderInfo->File->Layer[Idx]->TransformInfo, RenderInfo->CompBuffer, RenderRegion); +#else + if (InstructionMode == instruction_mode_avx) + AVX2_RenderLayer(RenderInfo->File->Layer[Idx]->TransformInfo, RenderInfo->CompBuffer, RenderRegion); + else if (InstructionMode == instruction_mode_sse) + SSE2_RenderLayer(RenderInfo->File->Layer[Idx]->TransformInfo, RenderInfo->CompBuffer, RenderRegion); +#endif + else + Fallback_RenderLayer(RenderInfo->File->Layer[Idx]->TransformInfo, RenderInfo->CompBuffer, RenderRegion); + } +} + +static void +FinishRenderAndUpload(project_state *State, comp_buffer *CompBuffer, GLuint textureID) +{ +#if PERF + Test = __rdtsc() - Test; + + Debug.PixelCountRendered = 1280*720*5; + printf("Cycles per pixel rendered: %li ", Test / Debug.PixelCountRendered); + printf("Pixels rendered: %li ", Debug.PixelCountRendered); + printf("Cycles: %li\n", Test); + + Test = 0; + Debug.PixelCountTransparent = 0; + Debug.PixelCountRendered = 0; + Debug.PixelCountChecked = 0; +#endif + + +#if PACKEDRGB + Bitmap_ConvertPacking(CompBuffer->PackedBuffer, CompBuffer->UnpackedBuffer, + CompBuffer->Width, CompBuffer->Height, CompBuffer->BytesPerPixel, 1); +#endif + EndRenderState(State); + glBindTexture(GL_TEXTURE_2D, textureID); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, CompBuffer->Width, CompBuffer->Height, GL_RGBA, GL_UNSIGNED_BYTE, + CompBuffer->UnpackedBuffer); + + // shmp->shared_framenumber = File.CurrentFrame; + // if (sem_post(&shmp->sem2) == -1) + // Assert(0); +} + +static void +QueueCurrentFrame(project_data *File, comp_buffer *CompBuffer, project_state *State) +{ + IsRendering = true; + render_queue RenderInfo = {File, State, CompBuffer}; + +#if PERF + Test = __rdtsc(); +#endif + + for (int16 i = 0; i < File->NumberOfLayers; i++) + { + if (File->Layer[i]->StartFrame <= File->CurrentFrame && + File->Layer[i]->EndFrame >= File->CurrentFrame) + { + File->Layer[i]->TransformInfo = CalculateTransforms(File->Layer[i], CompBuffer); + State->LayersToRender[State->NumberOfLayersToRender] = i; + State->NumberOfLayersToRender++; + } + } + + + uint16 TileWidth = CompBuffer->Width / 4; + uint16 TileHeight = CompBuffer->Height / 4; + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + // if (x == y) { + rectangle RenderRegion = {TileWidth*x, TileHeight*y, TileWidth + TileWidth*x, TileHeight + TileHeight*y}; + // The render regions always have to be aligned to the top left of + // a 4x4 chunk (at least for AVX2) and cannot exceed the bounds of + // the comp. + // It seems we don't need any special math to guarantee this aside + // from dividing by 4 and modulating. + RenderRegion.Min.x -= RenderRegion.Min.x % 4; + RenderRegion.Min.y -= RenderRegion.Min.y % 4; + RenderRegion.Max.x -= RenderRegion.Max.x % 4; + RenderRegion.Max.y -= RenderRegion.Max.y % 4; + if (RenderRegion.Max.x > CompBuffer->Width) + RenderRegion.Max.x = CompBuffer->Width; + if (RenderRegion.Max.y > CompBuffer->Height) + RenderRegion.Max.y = CompBuffer->Height; + PushRect(RenderRegion); + // } + } + } + + + rectangle RenderRegion = {0, 0, (int32)CompBuffer->Width, (int32)CompBuffer->Height}; + RenderLayers(&RenderInfo, RenderRegion); + +} + +#if ARM + +static void +NEON_RenderLayer(transform_info T, comp_buffer *Buffer, rectangle RenderRegion) +{ + rectangle LayerBounds = ClipRectangle( T.ClipRect, + RenderRegion ); + // Remember: since bitmaps are packed in 4x4 cubes, we always need to be aligned. + LayerBounds.Min.x -= LayerBounds.Min.x % 4; + LayerBounds.Min.y -= LayerBounds.Min.y % 4; + + uint16 WidthP, HeightP; + Bitmap_CalcPackedDimensions(Buffer->Width, Buffer->Height, &WidthP, &HeightP); + + uint8 *TexPTR = (uint8 *)T.SourceBuffer; + Assert(LayerBounds.Max.x <= Buffer->Width); + Assert(LayerBounds.Max.y <= Buffer->Height); + + float32x4_t XAxisPX = vdupq_n_f32(T.XAxisPX); + float32x4_t XAxisPY = vdupq_n_f32(T.XAxisPY); + float32x4_t YAxisPX = vdupq_n_f32(T.YAxisPX); + float32x4_t YAxisPY = vdupq_n_f32(T.YAxisPY); + + float32x4_t LayerWidth = vdupq_n_f32(T.LayerWidth); + int32x4_t FullLayerWidth4i = vdupq_n_s32(T.FullLayerWidth*4); + int32x4_t LayerWidthMinusOne = vdupq_n_s32(T.LayerWidth - 1); + int32x4_t LayerHeightMinusOne = vdupq_n_s32(T.LayerHeight - 1); + float32x4_t LayerHeight = vdupq_n_f32(T.LayerHeight); + float32x4_t LayerOpacity = vdupq_n_f32(T.LayerOpacity); + float32x4_t OriginX = vdupq_n_f32(T.OriginX); + float32x4_t OriginY = vdupq_n_f32(T.OriginY); + + float32x4_t ClipPrevent = vdupq_n_f32(0.001f); + float32x4_t One = vdupq_n_f32(1); + float32x4_t Two = vdupq_n_f32(2); + float32x4_t Zero = vdupq_n_f32(0); + + float32x4_t ZeroPoint25 = vdupq_n_f32(0.25); + float32x4_t ZeroPointFive = vdupq_n_f32(0.5); + int32x4_t Onei = vdupq_n_s32(1); + float32x4_t Four = vdupq_n_f32(4); + int32x4_t FF = vdupq_n_s32(0xFF); + int32x4_t BottomTwoBits = vdupq_n_s32(0x03); + int32x4_t Fouri = vdupq_n_s32(4); + int32x4_t Sixteeni = vdupq_n_s32(16); + float32x4_t Real255 = vdupq_n_f32(255.0f); + float32x4_t Norm255 = vdupq_n_f32(1/255.0f); + + // NOTE(fox): Each loop operates on 4 pixels, 4 horizontal by 1 vertical. + + // TODO(fox): A possible optimization could be made by using the 32x4x4 + // load intrinsic and a loop that repeats four times. + + for (int32 Y = LayerBounds.Min.y; Y < LayerBounds.Max.y; Y++) + { + real32 xvals[4] = { (real32)LayerBounds.Min.x, (real32)LayerBounds.Min.x+1, + (real32)LayerBounds.Min.x+2, (real32)LayerBounds.Min.x+3 }; + float32x4_t PixelX = vld1q_f32(xvals); + + float32x4_t PixelY = vdupq_n_f32((real32)Y); + float32x4_t StartVectorY = vsubq_f32(PixelY, OriginY); + + for (int32 X = LayerBounds.Min.x; X < LayerBounds.Max.x; X += 4) + { + + float32x4_t StartVectorX = vsubq_f32(PixelX, OriginX); + + uint32 XLookup = (X >> 2)*16 + (X % 4); + uint32 YLookup = (Y >> 2)*(WidthP*4) + (Y % 4)*4; + uint32 PixelToSeek = XLookup + YLookup; + uint8 *Pixel = (uint8 *)Buffer->PackedBuffer + PixelToSeek*Buffer->BytesPerPixel; + + float32x4_t U = vaddq_f32(vmulq_f32(StartVectorX, XAxisPX), vmulq_f32(StartVectorY, XAxisPY)); + float32x4_t V = vaddq_f32(vmulq_f32(StartVectorX, YAxisPX), vmulq_f32(StartVectorY, YAxisPY)); + + uint32x4_t LayerMask = vandq_u32(vandq_u32(vcgeq_f32(U, Zero), vcltq_f32(U, One)), + vandq_u32(vcgeq_f32(V, Zero), vcltq_f32(V, One))); + + // TODO(fox): Make more efficient with some sort of truncation + uint32 comp[4]; + vst1q_u32(comp, LayerMask); + if (comp[0] || comp[1] || comp[2] || comp[3]) { + U = vmaxq_f32(vminq_f32(One, U), Zero); + V = vmaxq_f32(vminq_f32(One, V), Zero); + + float32x4_t TexXFull = vmulq_f32(U, LayerWidth); + float32x4_t TexYFull = vmulq_f32(V, LayerHeight); + int32x4_t TexXInt = vcvtq_s32_f32(TexXFull); + int32x4_t TexXIntPlusOne = vaddq_f32(TexXInt, vandq_u32(vcltq_u32(TexXInt, LayerWidthMinusOne), Onei)); + int32x4_t TexYInt = vcvtq_s32_f32(TexYFull); + int32x4_t TexYIntPlusOne = vaddq_f32(TexYInt, vandq_u32(vcltq_u32(TexYInt, LayerWidthMinusOne), Onei)); + + float32x4_t TexX = vsubq_f32(TexXFull, vcvtq_f32_u32(TexXInt)); + float32x4_t TexY = vsubq_f32(TexYFull, vcvtq_f32_u32(TexYInt)); + float32x4_t TexXInv = vsubq_f32(One, TexX); + float32x4_t TexYInv = vsubq_f32(One, TexY); + float32x4_t TexBothXInv = vmulq_f32(TexXInv, TexY); + float32x4_t TexBothYInv = vmulq_f32(TexX, TexYInv); + float32x4_t TexBoth = vmulq_f32(TexY, TexX); + float32x4_t TexBothInv = vmulq_f32(TexXInv, TexYInv); + + int32x4_t XLookup = vaddq_u32(vmulq_u32(vshrq_n_u32(TexXInt, 2), Sixteeni), + vandq_u32(TexXInt, BottomTwoBits)); + int32x4_t YLookup = vaddq_u32(vmulq_u32(vshrq_n_u32(TexYInt, 2), FullLayerWidth4i), + vmulq_u32(vandq_u32(TexYInt, BottomTwoBits), Fouri)); + int32x4_t XLookupPlusOne = vaddq_u32(vmulq_u32(vshrq_n_u32(TexXIntPlusOne, 2), Sixteeni), + vandq_u32(TexXIntPlusOne, BottomTwoBits)); + int32x4_t YLookupPlusOne = vaddq_u32(vmulq_u32(vshrq_n_u32(TexYIntPlusOne, 2), FullLayerWidth4i), + vmulq_u32(vandq_u32(TexYIntPlusOne, BottomTwoBits), Fouri)); + + int32x4_t PixelLookupTL = vaddq_u32(XLookup, YLookup); + int32x4_t PixelLookupTR = vaddq_u32(XLookupPlusOne, YLookup); + int32x4_t PixelLookupBL = vaddq_u32(XLookup, YLookupPlusOne); + int32x4_t PixelLookupBR = vaddq_u32(XLookupPlusOne, YLookupPlusOne); + + // I thought NEON had gather/scatter, but it appears it doesn't... + } + + PixelX = vaddq_f32(PixelX, Four); + } + } +} + +#else + +#if 0 +#include "iacaMarks.h" +#else +#define IACA_START +#define IACA_END +#endif + +static void +AVX2_RenderLayer(transform_info T, comp_buffer *Buffer, rectangle RenderRegion) +{ + rectangle LayerBounds = ClipRectangle( T.ClipRect, + RenderRegion ); + // Remember: since bitmaps are packed in 4x4 cubes, we always need to be aligned. + LayerBounds.Min.x -= LayerBounds.Min.x % 4; + LayerBounds.Min.y -= LayerBounds.Min.y % 4; + + uint16 WidthP, HeightP; + Bitmap_CalcPackedDimensions(Buffer->Width, Buffer->Height, &WidthP, &HeightP); + + uint8 *TexPTR = (uint8 *)T.SourceBuffer; + Assert(LayerBounds.Max.x <= Buffer->Width); + Assert(LayerBounds.Max.y <= Buffer->Height); + + __m256 XAxisPX = _mm256_set1_ps(T.XAxisPX); + __m256 XAxisPY = _mm256_set1_ps(T.XAxisPY); + __m256 YAxisPX = _mm256_set1_ps(T.YAxisPX); + __m256 YAxisPY = _mm256_set1_ps(T.YAxisPY); + + __m256 LayerWidth = _mm256_set1_ps(T.LayerWidth); + __m256 LayerBoundsMaxX = _mm256_set1_ps(LayerBounds.Max.x); + __m256i FullLayerWidth4i = _mm256_set1_epi32(T.FullLayerWidth*4); + __m256i LayerWidthMinusOne = _mm256_set1_epi32(T.LayerWidth - 1); + __m256i LayerHeightMinusOne = _mm256_set1_epi32(T.LayerHeight - 1); + __m256 LayerHeight = _mm256_set1_ps(T.LayerHeight); + __m256 LayerOpacity = _mm256_set1_ps(T.LayerOpacity); + __m256 OriginX = _mm256_set1_ps(T.OriginX); + __m256 OriginY = _mm256_set1_ps(T.OriginY); + + __m256 ClipPrevent = _mm256_set1_ps(0.001f); + __m256 One = _mm256_set1_ps(1); + __m256 Two = _mm256_set1_ps(2); + __m256 Zero = _mm256_set1_ps(0); + + __m256 ZeroPoint25 = _mm256_set1_ps(0.25); + __m256 ZeroPointFive = _mm256_set1_ps(0.5); + __m256i Onei = _mm256_set1_epi32(1); + __m256 Four = _mm256_set1_ps(4); + __m256 Eight = _mm256_set1_ps(8); + __m256i FF = _mm256_set1_epi32(0xFF); + __m256i BottomTwoBits = _mm256_set1_epi32(0x03); + __m256i Fouri = _mm256_set1_epi32(4); + __m256i Sixteeni = _mm256_set1_epi32(16); + __m256 Real255 = _mm256_set1_ps(255.0f); + __m256 Norm255 = _mm256_set1_ps(1/255.0f); + // __m256i White = _mm256_setr_epi32(0xFFFFFFFF, 0, 0, 0, 0xFFFFFFFF, 0, 0, 0); + // __m256i White2 = _mm256_set1_epi32(0xFFFFFFFF); + + // TODO(fox): Tried an MSAA technique for anti aliasing, but it still looks pretty sucky. + __m256 X0 = _mm256_set1_ps(0.30); + __m256 Y0 = _mm256_set1_ps(0.10); + __m256 X1 = _mm256_set1_ps(0.80); + __m256 Y1 = _mm256_set1_ps(0.35); + __m256 X2 = _mm256_set1_ps(0.05); + __m256 Y2 = _mm256_set1_ps(0.60); + __m256 X3 = _mm256_set1_ps(0.55); + __m256 Y3 = _mm256_set1_ps(0.85); + + +#if PACKEDRGB +#else + __m256i LayerPitch = _mm256_set1_epi32(T.LayerPitch); + __m256i BytesPerPixel = _mm256_set1_epi32(Buffer->BytesPerPixel); +#endif + +#if PACKEDRGB + for (int32 Y = LayerBounds.Min.y; Y < LayerBounds.Max.y; Y+=2) + { + __m256 PixelX = _mm256_setr_ps((real32)LayerBounds.Min.x, + (real32)LayerBounds.Min.x+1, + (real32)LayerBounds.Min.x+2, + (real32)LayerBounds.Min.x+3, + (real32)LayerBounds.Min.x, + (real32)LayerBounds.Min.x+1, + (real32)LayerBounds.Min.x+2, + (real32)LayerBounds.Min.x+3); + + __m256 PixelY = _mm256_setr_ps((real32)Y, + (real32)Y, + (real32)Y, + (real32)Y, + (real32)Y+1, + (real32)Y+1, + (real32)Y+1, + (real32)Y+1); +#else + for (int32 Y = LayerBounds.Min.y; Y < LayerBounds.Max.y; Y++) + { + __m256 PixelX = _mm256_setr_ps((real32)LayerBounds.Min.x, + (real32)LayerBounds.Min.x+1, + (real32)LayerBounds.Min.x+2, + (real32)LayerBounds.Min.x+3, + (real32)LayerBounds.Min.x+4, + (real32)LayerBounds.Min.x+5, + (real32)LayerBounds.Min.x+6, + (real32)LayerBounds.Min.x+7); + + __m256 PixelY = _mm256_set1_ps((real32)Y); +#endif + + __m256 StartVectorY = _mm256_sub_ps(PixelY, OriginY); + +#if PACKEDRGB + for (int32 X = LayerBounds.Min.x; X < LayerBounds.Max.x; X += 4) +#else + for (int32 X = LayerBounds.Min.x; X < LayerBounds.Max.x; X += 8) +#endif + { + + IACA_START; + + __m256 StartVectorX = _mm256_sub_ps(PixelX, OriginX); + __m256 StartVectorX0 = _mm256_add_ps(StartVectorX, X0); + __m256 StartVectorY0 = _mm256_add_ps(StartVectorY, Y0); + __m256 StartVectorX1 = _mm256_add_ps(StartVectorX, X1); + __m256 StartVectorY1 = _mm256_add_ps(StartVectorY, Y1); + __m256 StartVectorX2 = _mm256_add_ps(StartVectorX, X2); + __m256 StartVectorY2 = _mm256_add_ps(StartVectorY, Y2); + __m256 StartVectorX3 = _mm256_add_ps(StartVectorX, X3); + __m256 StartVectorY3 = _mm256_add_ps(StartVectorY, Y3); + +#if PACKEDRGB + uint32 XLookup = (X >> 2)*16 + (X % 4); + uint32 YLookup = (Y >> 2)*(WidthP*4) + (Y % 4)*4; + uint32 PixelToSeek = XLookup + YLookup; + uint8 *Pixel = (uint8 *)Buffer->PackedBuffer + PixelToSeek*Buffer->BytesPerPixel; +#else + uint8 *Pixel = (uint8 *)Buffer->UnpackedBuffer + Y*T.BufferPitch + X*Buffer->BytesPerPixel; +#endif + + __m256 U = _mm256_add_ps(_mm256_mul_ps(StartVectorX, XAxisPX), _mm256_mul_ps(StartVectorY, XAxisPY)); + __m256 V = _mm256_add_ps(_mm256_mul_ps(StartVectorX, YAxisPX), _mm256_mul_ps(StartVectorY, YAxisPY)); + + __m256 U0 = _mm256_add_ps(_mm256_mul_ps(StartVectorX0, XAxisPX), _mm256_mul_ps(StartVectorY0, XAxisPY)); + __m256 V0 = _mm256_add_ps(_mm256_mul_ps(StartVectorX0, YAxisPX), _mm256_mul_ps(StartVectorY0, YAxisPY)); + __m256 U1 = _mm256_add_ps(_mm256_mul_ps(StartVectorX1, XAxisPX), _mm256_mul_ps(StartVectorY1, XAxisPY)); + __m256 V1 = _mm256_add_ps(_mm256_mul_ps(StartVectorX1, YAxisPX), _mm256_mul_ps(StartVectorY1, YAxisPY)); + __m256 U2 = _mm256_add_ps(_mm256_mul_ps(StartVectorX2, XAxisPX), _mm256_mul_ps(StartVectorY2, XAxisPY)); + __m256 V2 = _mm256_add_ps(_mm256_mul_ps(StartVectorX2, YAxisPX), _mm256_mul_ps(StartVectorY2, YAxisPY)); + __m256 U3 = _mm256_add_ps(_mm256_mul_ps(StartVectorX3, XAxisPX), _mm256_mul_ps(StartVectorY3, XAxisPY)); + __m256 V3 = _mm256_add_ps(_mm256_mul_ps(StartVectorX3, YAxisPX), _mm256_mul_ps(StartVectorY3, YAxisPY)); + + __m256 LayerMask0 = _mm256_and_ps(_mm256_and_ps(_mm256_cmp_ps(U0, Zero, 13), _mm256_cmp_ps(U0, One, 1)), + _mm256_and_ps(_mm256_cmp_ps(V0, Zero, 13), _mm256_cmp_ps(V0, One, 1))); + __m256 LayerMask1 = _mm256_and_ps(_mm256_and_ps(_mm256_cmp_ps(U1, Zero, 13), _mm256_cmp_ps(U1, One, 1)), + _mm256_and_ps(_mm256_cmp_ps(V1, Zero, 13), _mm256_cmp_ps(V1, One, 1))); + __m256 LayerMask2 = _mm256_and_ps(_mm256_and_ps(_mm256_cmp_ps(U2, Zero, 13), _mm256_cmp_ps(U2, One, 1)), + _mm256_and_ps(_mm256_cmp_ps(V2, Zero, 13), _mm256_cmp_ps(V2, One, 1))); + __m256 LayerMask3 = _mm256_and_ps(_mm256_and_ps(_mm256_cmp_ps(U3, Zero, 13), _mm256_cmp_ps(U3, One, 1)), + _mm256_and_ps(_mm256_cmp_ps(V3, Zero, 13), _mm256_cmp_ps(V3, One, 1))); + + // Each point that passes adds .25 + __m256 Avg = _mm256_add_ps(_mm256_add_ps(_mm256_and_ps(LayerMask0, ZeroPoint25), _mm256_and_ps(LayerMask1, ZeroPoint25)), + _mm256_add_ps(_mm256_and_ps(LayerMask2, ZeroPoint25), _mm256_and_ps(LayerMask3, ZeroPoint25))); + + // Preventing overlap between threads for non-packed. One nice thing + // about packed is that the 4-padded bitmap means we can set up the + // boundaries so we don't have to check this ever. + __m256i TileBarrier = _mm256_cvtps_epi32(_mm256_cmp_ps(PixelX, LayerBoundsMaxX, 13)); + + // Zero - no points pass + // One - all points pass; not an edge + __m256i Mask = _mm256_cvtps_epi32(_mm256_cmp_ps(Avg, Zero, 14)); + __m256i NonEdge = _mm256_cvtps_epi32(_mm256_cmp_ps(Avg, One, 13)); + __m256i TotalMask = _mm256_andnot_si256(TileBarrier, _mm256_and_si256(Mask, NonEdge)); + + // __m256 LayerMask = _mm256_and_ps(_mm256_and_ps(_mm256_cmp_ps(U, Zero, 13), _mm256_cmp_ps(U, One, 1)), + // _mm256_and_ps(_mm256_cmp_ps(V, Zero, 13), _mm256_cmp_ps(V, One, 1))); + + // If all of the pixels are zeroed in the mask (aka fall outside + // the UV lookup), we can skip the iteration. + if (_mm256_movemask_epi8(TotalMask)) + { + __m256i EdgeMask = _mm256_andnot_si256(NonEdge, Mask); + + U = _mm256_max_ps(_mm256_min_ps(One, U), Zero); + V = _mm256_max_ps(_mm256_min_ps(One, V), Zero); + + __m256 TexXFull = _mm256_mul_ps(U, LayerWidth); + __m256 TexYFull = _mm256_mul_ps(V, LayerHeight); + __m256i TexXInt = _mm256_cvttps_epi32(TexXFull); + __m256i TexYInt = _mm256_cvttps_epi32(TexYFull); + __m256i TexXIntPlusOne = _mm256_add_epi32(TexXInt, _mm256_and_si256(_mm256_cmpgt_epi32(LayerWidthMinusOne, TexXInt), Onei)); + __m256i TexYIntPlusOne = _mm256_add_epi32(TexYInt, _mm256_and_si256(_mm256_cmpgt_epi32(LayerHeightMinusOne, TexYInt), Onei)); + // NOTE(fox): The comparison is for when we're on the last pixel of the texel. + + __m256 TexX = _mm256_sub_ps(TexXFull, _mm256_cvtepi32_ps(TexXInt)); + __m256 TexY = _mm256_sub_ps(TexYFull, _mm256_cvtepi32_ps(TexYInt)); + __m256 TexXInv = _mm256_sub_ps(One, TexX); + __m256 TexYInv = _mm256_sub_ps(One, TexY); + __m256 TexBothXInv = _mm256_mul_ps(TexXInv, TexY); + __m256 TexBothYInv = _mm256_mul_ps(TexX, TexYInv); + __m256 TexBoth = _mm256_mul_ps(TexY, TexX); + __m256 TexBothInv = _mm256_mul_ps(TexXInv, TexYInv); + +#if PACKEDRGB + __m256i XLookup = _mm256_add_epi32(_mm256_mullo_epi32(_mm256_srli_epi32(TexXInt, 2), Sixteeni), + _mm256_and_si256(TexXInt, BottomTwoBits)); + __m256i YLookup = _mm256_add_epi32(_mm256_mullo_epi32(_mm256_srli_epi32(TexYInt, 2), FullLayerWidth4i), + _mm256_mullo_epi32(_mm256_and_si256(TexYInt, BottomTwoBits), Fouri)); + __m256i XLookupPlusOne = _mm256_add_epi32(_mm256_mullo_epi32(_mm256_srli_epi32(TexXIntPlusOne, 2), Sixteeni), + _mm256_and_si256(TexXIntPlusOne, BottomTwoBits)); + __m256i YLookupPlusOne = _mm256_add_epi32(_mm256_mullo_epi32(_mm256_srli_epi32(TexYIntPlusOne, 2), FullLayerWidth4i), + _mm256_mullo_epi32(_mm256_and_si256(TexYIntPlusOne, BottomTwoBits), Fouri)); +#else + __m256i XLookup = TexXInt; + __m256i YLookup = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_cvtepi32_ps(TexYInt), LayerWidth)); + __m256i XLookupPlusOne = TexXIntPlusOne; + __m256i YLookupPlusOne = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_cvtepi32_ps(TexYIntPlusOne), LayerWidth)); +#endif + + __m256i PixelLookupTL = _mm256_add_epi32(XLookup, YLookup); + __m256i PixelLookupTR = _mm256_add_epi32(XLookupPlusOne, YLookup); + __m256i PixelLookupBL = _mm256_add_epi32(XLookup, YLookupPlusOne); + __m256i PixelLookupBR = _mm256_add_epi32(XLookupPlusOne, YLookupPlusOne); + + // The big feature of AVX2: gathering. + __m256i PixelsTL = _mm256_i32gather_epi32((const int32 *)TexPTR, PixelLookupTL, 4); + __m256i PixelsTR = _mm256_i32gather_epi32((const int32 *)TexPTR, PixelLookupTR, 4); + __m256i PixelsBL = _mm256_i32gather_epi32((const int32 *)TexPTR, PixelLookupBL, 4); + __m256i PixelsBR = _mm256_i32gather_epi32((const int32 *)TexPTR, PixelLookupBR, 4); + + __m256 R_TexTL = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256( PixelsTL, FF)), Norm255); + __m256 G_TexTL = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsTL, 8), FF)), Norm255); + __m256 B_TexTL = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsTL, 16), FF)), Norm255); + __m256 A_TexTL = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsTL, 24), FF)), Norm255); + + __m256 R_TexTR = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256( PixelsTR, FF)), Norm255); + __m256 G_TexTR = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsTR, 8), FF)), Norm255); + __m256 B_TexTR = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsTR, 16), FF)), Norm255); + __m256 A_TexTR = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsTR, 24), FF)), Norm255); + + __m256 R_TexBL = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256( PixelsBL, FF)), Norm255); + __m256 G_TexBL = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsBL, 8), FF)), Norm255); + __m256 B_TexBL = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsBL, 16), FF)), Norm255); + __m256 A_TexBL = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsBL, 24), FF)), Norm255); + + __m256 R_TexBR = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256( PixelsBR, FF)), Norm255); + __m256 G_TexBR = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsBR, 8), FF)), Norm255); + __m256 B_TexBR = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsBR, 16), FF)), Norm255); + __m256 A_TexBR = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(PixelsBR, 24), FF)), Norm255); + + __m256 R_Col = _mm256_add_ps(_mm256_add_ps(_mm256_mul_ps(TexBothInv, R_TexTL), + _mm256_mul_ps(TexBothYInv, R_TexTR)), + _mm256_add_ps(_mm256_mul_ps(TexBothXInv, R_TexBL), + _mm256_mul_ps(TexBoth, R_TexBR))); + __m256 G_Col = _mm256_add_ps(_mm256_add_ps(_mm256_mul_ps(TexBothInv, G_TexTL), + _mm256_mul_ps(TexBothYInv, G_TexTR)), + _mm256_add_ps(_mm256_mul_ps(TexBothXInv, G_TexBL), + _mm256_mul_ps(TexBoth, G_TexBR))); + __m256 B_Col = _mm256_add_ps(_mm256_add_ps(_mm256_mul_ps(TexBothInv, B_TexTL), + _mm256_mul_ps(TexBothYInv, B_TexTR)), + _mm256_add_ps(_mm256_mul_ps(TexBothXInv, B_TexBL), + _mm256_mul_ps(TexBoth, B_TexBR))); + __m256 A_Col = _mm256_add_ps(_mm256_add_ps(_mm256_mul_ps(TexBothInv, A_TexTL), + _mm256_mul_ps(TexBothYInv, A_TexTR)), + _mm256_add_ps(_mm256_mul_ps(TexBothXInv, A_TexBL), + _mm256_mul_ps(TexBoth, A_TexBR))); + + // Apply anti-aliasing to edges if there are any + if (_mm256_movemask_epi8(EdgeMask)) + { + A_Col = _mm256_blendv_ps(A_Col, _mm256_mul_ps(A_Col, Avg), _mm256_cvtepi32_ps(EdgeMask)); + } + + IACA_END; + __m256 LayerAlpha = _mm256_mul_ps(A_Col, LayerOpacity); + __m256 LayerAlphaInv = _mm256_sub_ps(One, LayerAlpha); + + // Hoisted out of some blend modes; maybe it'd be better to just keep them in there. + __m256 R_Colx2 = _mm256_mul_ps(R_Col, Two); + __m256 R_ColInv = _mm256_sub_ps(One, R_Col); + + __m256 G_Colx2 = _mm256_mul_ps(G_Col, Two); + __m256 G_ColInv = _mm256_sub_ps(One, G_Col); + + __m256 B_Colx2 = _mm256_mul_ps(B_Col, Two); + __m256 B_ColInv = _mm256_sub_ps(One, B_Col); + + __m256 R_Blend = R_Col; + __m256 G_Blend = G_Col; + __m256 B_Blend = B_Col; + __m256 A_Blend = LayerAlpha; + + // Only load the dest pixel if we actually need to (a pixel's opacity isn't 255 or the blend mode requires it). + if (T.BlendMode != blend_normal || _mm256_movemask_epi8(_mm256_cvtps_epi32(_mm256_cmp_ps(LayerAlpha, One, 2)))) + { + __m256i DestPixel = _mm256_loadu_si256((const __m256i *)Pixel); + __m256 R_Dest = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256( DestPixel, FF)), Norm255); + __m256 G_Dest = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(DestPixel, 8), FF)), Norm255); + __m256 B_Dest = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(DestPixel, 16), FF)), Norm255); + __m256 A_Dest = _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srli_epi32(DestPixel, 24), FF)), Norm255); + + switch (T.BlendMode) + { + case blend_normal: + { + } break; + case blend_multiply: + { + R_Blend = _mm256_mul_ps(R_Dest, R_Col); + G_Blend = _mm256_mul_ps(G_Dest, G_Col); + B_Blend = _mm256_mul_ps(B_Dest, B_Col); + } break; + case blend_colorburn: + { + // NOTE(fox): A small amount is added to Col since images with zero for alpha may also zero out the + // color channels, causing black clipping. + R_Blend = _mm256_sub_ps(One, _mm256_div_ps(_mm256_sub_ps(One, R_Dest), _mm256_add_ps(R_Col, ClipPrevent))); + G_Blend = _mm256_sub_ps(One, _mm256_div_ps(_mm256_sub_ps(One, G_Dest), _mm256_add_ps(G_Col, ClipPrevent))); + B_Blend = _mm256_sub_ps(One, _mm256_div_ps(_mm256_sub_ps(One, B_Dest), _mm256_add_ps(B_Col, ClipPrevent))); + } break; + case blend_linearburn: + { + R_Blend = _mm256_sub_ps(_mm256_add_ps(R_Dest, R_Col), One); + G_Blend = _mm256_sub_ps(_mm256_add_ps(G_Dest, G_Col), One); + B_Blend = _mm256_sub_ps(_mm256_add_ps(B_Dest, B_Col), One); + } break; + case blend_add: + { + R_Blend = _mm256_add_ps(R_Dest, R_Col); + G_Blend = _mm256_add_ps(G_Dest, G_Col); + B_Blend = _mm256_add_ps(B_Dest, B_Col); + } break; + case blend_screen: + { + R_Blend = _mm256_sub_ps(One, _mm256_mul_ps(_mm256_sub_ps(One, R_Dest), R_ColInv)); + G_Blend = _mm256_sub_ps(One, _mm256_mul_ps(_mm256_sub_ps(One, G_Dest), G_ColInv)); + B_Blend = _mm256_sub_ps(One, _mm256_mul_ps(_mm256_sub_ps(One, B_Dest), B_ColInv)); + } break; + case blend_overlay: + { + __m256 R_Mask = _mm256_cmp_ps(R_Dest, ZeroPointFive, 1); + __m256 G_Mask = _mm256_cmp_ps(G_Dest, ZeroPointFive, 1); + __m256 B_Mask = _mm256_cmp_ps(B_Dest, ZeroPointFive, 1); + __m256 R_Lower = _mm256_mul_ps(Two, _mm256_mul_ps(R_Dest, R_Col)); + __m256 G_Lower = _mm256_mul_ps(Two, _mm256_mul_ps(G_Dest, G_Col)); + __m256 B_Lower = _mm256_mul_ps(Two, _mm256_mul_ps(B_Dest, B_Col)); + __m256 R_Upper = _mm256_sub_ps(One, _mm256_mul_ps(Two, _mm256_mul_ps(_mm256_sub_ps(One, R_Dest), R_ColInv))); + __m256 G_Upper = _mm256_sub_ps(One, _mm256_mul_ps(Two, _mm256_mul_ps(_mm256_sub_ps(One, G_Dest), G_ColInv))); + __m256 B_Upper = _mm256_sub_ps(One, _mm256_mul_ps(Two, _mm256_mul_ps(_mm256_sub_ps(One, B_Dest), B_ColInv))); + R_Blend = _mm256_blendv_ps(R_Upper, R_Lower, R_Mask); + G_Blend = _mm256_blendv_ps(G_Upper, G_Lower, G_Mask); + B_Blend = _mm256_blendv_ps(B_Upper, B_Lower, B_Mask); + } break; + case blend_softlight: + { + // using Pegtop's equation + R_Blend = _mm256_add_ps(_mm256_mul_ps(_mm256_sub_ps(One, R_Colx2), _mm256_mul_ps(R_Dest, R_Dest)), _mm256_mul_ps(R_Colx2, R_Dest)); + G_Blend = _mm256_add_ps(_mm256_mul_ps(_mm256_sub_ps(One, G_Colx2), _mm256_mul_ps(G_Dest, G_Dest)), _mm256_mul_ps(G_Colx2, G_Dest)); + B_Blend = _mm256_add_ps(_mm256_mul_ps(_mm256_sub_ps(One, B_Colx2), _mm256_mul_ps(B_Dest, B_Dest)), _mm256_mul_ps(B_Colx2, B_Dest)); + } break; + case blend_hardlight: + { + __m256 R_Mask = _mm256_cmp_ps(R_Dest, ZeroPointFive, 13); + __m256 G_Mask = _mm256_cmp_ps(G_Dest, ZeroPointFive, 13); + __m256 B_Mask = _mm256_cmp_ps(B_Dest, ZeroPointFive, 13); + __m256 R_Lower = _mm256_mul_ps(Two, _mm256_mul_ps(R_Dest, R_Col)); + __m256 G_Lower = _mm256_mul_ps(Two, _mm256_mul_ps(G_Dest, G_Col)); + __m256 B_Lower = _mm256_mul_ps(Two, _mm256_mul_ps(B_Dest, B_Col)); + __m256 R_Upper = _mm256_sub_ps(One, _mm256_mul_ps(Two, _mm256_mul_ps(_mm256_sub_ps(One, R_Dest), R_ColInv))); + __m256 G_Upper = _mm256_sub_ps(One, _mm256_mul_ps(Two, _mm256_mul_ps(_mm256_sub_ps(One, G_Dest), G_ColInv))); + __m256 B_Upper = _mm256_sub_ps(One, _mm256_mul_ps(Two, _mm256_mul_ps(_mm256_sub_ps(One, B_Dest), B_ColInv))); + R_Blend = _mm256_blendv_ps(R_Upper, R_Lower, R_Mask); + G_Blend = _mm256_blendv_ps(G_Upper, G_Lower, G_Mask); + B_Blend = _mm256_blendv_ps(B_Upper, B_Lower, B_Mask); + } break; + case blend_subtract: + { + R_Blend = _mm256_sub_ps(R_Dest, R_Col); + G_Blend = _mm256_sub_ps(G_Dest, G_Col); + B_Blend = _mm256_sub_ps(B_Dest, B_Col); + } break; + case blend_divide: + { + R_Blend = _mm256_div_ps(R_Dest, _mm256_add_ps(R_Col, ClipPrevent)); + G_Blend = _mm256_div_ps(G_Dest, _mm256_add_ps(G_Col, ClipPrevent)); + B_Blend = _mm256_div_ps(B_Dest, _mm256_add_ps(B_Col, ClipPrevent)); + } break; + case blend_difference: + { + __m256 R_Lower = _mm256_sub_ps(R_Col, R_Dest); + __m256 G_Lower = _mm256_sub_ps(G_Col, G_Dest); + __m256 B_Lower = _mm256_sub_ps(B_Col, B_Dest); + __m256 R_Upper = _mm256_sub_ps(R_Dest, R_Col); + __m256 G_Upper = _mm256_sub_ps(G_Dest, G_Col); + __m256 B_Upper = _mm256_sub_ps(B_Dest, B_Col); + __m256 R_Mask = _mm256_cmp_ps(R_Lower, Zero, 14); + __m256 G_Mask = _mm256_cmp_ps(G_Lower, Zero, 14); + __m256 B_Mask = _mm256_cmp_ps(B_Lower, Zero, 14); + R_Blend = _mm256_blendv_ps(R_Upper, R_Lower, R_Mask); + G_Blend = _mm256_blendv_ps(G_Upper, G_Lower, G_Mask); + B_Blend = _mm256_blendv_ps(B_Upper, B_Lower, B_Mask); + } break; + } + + R_Blend = _mm256_add_ps(_mm256_mul_ps(R_Dest, LayerAlphaInv), _mm256_mul_ps(R_Blend, LayerAlpha)); + G_Blend = _mm256_add_ps(_mm256_mul_ps(G_Dest, LayerAlphaInv), _mm256_mul_ps(G_Blend, LayerAlpha)); + B_Blend = _mm256_add_ps(_mm256_mul_ps(B_Dest, LayerAlphaInv), _mm256_mul_ps(B_Blend, LayerAlpha)); + + // Standard behavior in photo apps is for blend modes to + // inherit underlying opacity instead of adding to it. + if (T.BlendMode == blend_normal) + A_Blend = _mm256_add_ps(A_Dest, LayerAlpha); + else + A_Blend = A_Dest; + } + + __m256i R_Out = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_max_ps(_mm256_min_ps(One, R_Blend), Zero), Real255)); + __m256i G_Out = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_max_ps(_mm256_min_ps(One, G_Blend), Zero), Real255)); + __m256i B_Out = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_max_ps(_mm256_min_ps(One, B_Blend), Zero), Real255)); + __m256i A_Out = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_max_ps(_mm256_min_ps(One, A_Blend), Zero), Real255)); + + __m256i OutputPixel = _mm256_or_si256( + _mm256_or_si256(R_Out, _mm256_slli_epi32(G_Out, 8)), + _mm256_or_si256(_mm256_slli_epi32(B_Out, 16), _mm256_slli_epi32(A_Out, 24))); + + _mm256_maskstore_epi32((int *)Pixel, TotalMask, OutputPixel); + } +#if PACKEDRGB + PixelX = _mm256_add_ps(PixelX, Four); +#else + PixelX = _mm256_add_ps(PixelX, Eight); +#endif + } + } +} + +static void +SSE2_RenderLayer(transform_info T, comp_buffer *Buffer, rectangle RenderRegion) +{ + rectangle LayerBounds = ClipRectangle( T.ClipRect, + RenderRegion ); + // Remember: since bitmaps are packed in 4x4 cubes, we always need to be aligned. + LayerBounds.Min.x -= LayerBounds.Min.x % 4; + LayerBounds.Min.y -= LayerBounds.Min.y % 4; + + uint16 WidthP, HeightP; + Bitmap_CalcPackedDimensions(Buffer->Width, Buffer->Height, &WidthP, &HeightP); + + uint8 *TexPTR = (uint8 *)T.SourceBuffer; + Assert(LayerBounds.Max.x <= Buffer->Width); + Assert(LayerBounds.Max.y <= Buffer->Height); + + __m128 XAxisPX = _mm_set1_ps(T.XAxisPX); + __m128 XAxisPY = _mm_set1_ps(T.XAxisPY); + __m128 YAxisPX = _mm_set1_ps(T.YAxisPX); + __m128 YAxisPY = _mm_set1_ps(T.YAxisPY); + + __m128 LayerWidth = _mm_set1_ps(T.LayerWidth); + __m128i LayerWidthMinusOne = _mm_set1_epi32(T.LayerWidth - 1); + __m128i FullLayerWidth4i = _mm_set1_epi32(T.FullLayerWidth*4); + __m128 LayerHeight = _mm_set1_ps(T.LayerHeight); + __m128i LayerHeightMinusOne = _mm_set1_epi32(T.LayerHeight - 1); + __m128 LayerOpacity = _mm_set1_ps(T.LayerOpacity); + __m128 OriginX = _mm_set1_ps(T.OriginX); + __m128 OriginY = _mm_set1_ps(T.OriginY); + + __m128 ClipPrevent = _mm_set1_ps(0.001f); + __m128 One = _mm_set1_ps(1); + __m128 Two = _mm_set1_ps(2); + __m128 Zero = _mm_set1_ps(0); + __m128 ZeroPointFive = _mm_set1_ps(0.5); + __m128i Onei = _mm_set1_epi32(1); + __m128 Four = _mm_set1_ps(4); + __m128i FF = _mm_set1_epi32(0xFF); + __m128i BottomTwoBits = _mm_set1_epi32(0x03); + __m128i Fouri = _mm_set1_epi32(4); + __m128i Sixteeni = _mm_set1_epi32(16); + __m128 Reg255 = _mm_set1_ps(255.0f); + __m128 Norm255 = _mm_set1_ps(1/255.0f); + + // NOTE(fox): Each loop operates on 4 pixels, 4 horizontal by 1 vertical. + + for (int32 Y = LayerBounds.Min.y; Y < LayerBounds.Max.y; Y++) + { + __m128 PixelX = _mm_setr_ps((real32)LayerBounds.Min.x, + (real32)LayerBounds.Min.x+1, + (real32)LayerBounds.Min.x+2, + (real32)LayerBounds.Min.x+3); + + __m128 PixelY = _mm_set1_ps((real32)Y); + __m128 StartVectorY = _mm_sub_ps(PixelY, OriginY); + + for (int32 X = LayerBounds.Min.x; X < LayerBounds.Max.x; X += 4) + { + + __m128 StartVectorX = _mm_sub_ps(PixelX, OriginX); + + uint32 XLookup = (X >> 2)*16 + (X % 4); + uint32 YLookup = (Y >> 2)*(WidthP*4) + (Y % 4)*4; + uint32 PixelToSeek = XLookup + YLookup; + uint8 *Pixel = (uint8 *)Buffer->PackedBuffer + PixelToSeek*Buffer->BytesPerPixel; + + __m128 U = _mm_add_ps(_mm_mul_ps(StartVectorX, XAxisPX), _mm_mul_ps(StartVectorY, XAxisPY)); + __m128 V = _mm_add_ps(_mm_mul_ps(StartVectorX, YAxisPX), _mm_mul_ps(StartVectorY, YAxisPY)); + + __m128i LayerMask = _mm_castps_si128(_mm_and_ps(_mm_and_ps(_mm_cmpge_ps(U, Zero), _mm_cmplt_ps(U, One)), + _mm_and_ps(_mm_cmpge_ps(V, Zero), _mm_cmplt_ps(V, One)))); + + if (_mm_movemask_epi8(LayerMask)) + { + U = _mm_max_ps(_mm_min_ps(One, U), Zero); + V = _mm_max_ps(_mm_min_ps(One, V), Zero); + + __m128 TexXFull = _mm_mul_ps(U, LayerWidth); + __m128 TexYFull = _mm_mul_ps(V, LayerHeight); + __m128i TexXInt = _mm_cvttps_epi32(TexXFull); + __m128i TexXIntPlusOne = _mm_add_epi32(TexXInt, _mm_and_si128(_mm_cmplt_epi32(TexXInt, LayerWidthMinusOne), Onei)); + __m128i TexYInt = _mm_cvttps_epi32(TexYFull); + __m128i TexYIntPlusOne = _mm_add_epi32(TexYInt, _mm_and_si128(_mm_cmplt_epi32(TexYInt, LayerHeightMinusOne), Onei)); + + __m128 TexX = _mm_sub_ps(TexXFull, _mm_cvtepi32_ps(TexXInt)); + __m128 TexY = _mm_sub_ps(TexYFull, _mm_cvtepi32_ps(TexYInt)); + __m128 TexXInv = _mm_sub_ps(One, TexX); + __m128 TexYInv = _mm_sub_ps(One, TexY); + __m128 TexBothXInv = _mm_mul_ps(TexXInv, TexY); + __m128 TexBothYInv = _mm_mul_ps(TexX, TexYInv); + __m128 TexBoth = _mm_mul_ps(TexY, TexX); + __m128 TexBothInv = _mm_mul_ps(TexXInv, TexYInv); + + __m128i XLookup = _mm_add_epi32(_mm_mullo_epi32(_mm_srli_epi32(TexXInt, 2), Sixteeni), + _mm_and_si128(TexXInt, BottomTwoBits)); + __m128i YLookup = _mm_add_epi32(_mm_mullo_epi32(_mm_srli_epi32(TexYInt, 2), FullLayerWidth4i), + _mm_mullo_epi32(_mm_and_si128(TexYInt, BottomTwoBits), Fouri)); + __m128i XLookupPlusOne = _mm_add_epi32(_mm_mullo_epi32(_mm_srli_epi32(TexXIntPlusOne, 2), Sixteeni), + _mm_and_si128(TexXIntPlusOne, BottomTwoBits)); + __m128i YLookupPlusOne = _mm_add_epi32(_mm_mullo_epi32(_mm_srli_epi32(TexYIntPlusOne, 2), FullLayerWidth4i), + _mm_mullo_epi32(_mm_and_si128(TexYIntPlusOne, BottomTwoBits), Fouri)); + + __m128i PixelLookupTL = _mm_add_epi32(XLookup, YLookup); + __m128i PixelLookupTR = _mm_add_epi32(XLookupPlusOne, YLookup); + __m128i PixelLookupBL = _mm_add_epi32(XLookup, YLookupPlusOne); + __m128i PixelLookupBR = _mm_add_epi32(XLookupPlusOne, YLookupPlusOne); + + // SSE lacks gathering, so we have no choice but to manually + // look up each pixel's four bilinear samples in scalar. + + uint32 S_PixelLookupTL0 = _mm_cvtsi128_si32(PixelLookupTL); + uint32 S_PixelLookupTR0 = _mm_cvtsi128_si32(PixelLookupTR); + uint32 S_PixelLookupBL0 = _mm_cvtsi128_si32(PixelLookupBL); + uint32 S_PixelLookupBR0 = _mm_cvtsi128_si32(PixelLookupBR); + uint32 S_PixelsTL0 = *(uint32 *)(TexPTR + S_PixelLookupTL0*4); + uint32 S_PixelsTR0 = *(uint32 *)(TexPTR + S_PixelLookupTR0*4); + uint32 S_PixelsBL0 = *(uint32 *)(TexPTR + S_PixelLookupBL0*4); + uint32 S_PixelsBR0 = *(uint32 *)(TexPTR + S_PixelLookupBR0*4); + + uint32 S_PixelLookupTL1 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupTL, 4)); + uint32 S_PixelLookupTR1 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupTR, 4)); + uint32 S_PixelLookupBL1 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupBL, 4)); + uint32 S_PixelLookupBR1 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupBR, 4)); + uint32 S_PixelsTL1 = *(uint32 *)(TexPTR + S_PixelLookupTL1*4); + uint32 S_PixelsTR1 = *(uint32 *)(TexPTR + S_PixelLookupTR1*4); + uint32 S_PixelsBL1 = *(uint32 *)(TexPTR + S_PixelLookupBL1*4); + uint32 S_PixelsBR1 = *(uint32 *)(TexPTR + S_PixelLookupBR1*4); + + uint32 S_PixelLookupTL2 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupTL, 8)); + uint32 S_PixelLookupTR2 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupTR, 8)); + uint32 S_PixelLookupBL2 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupBL, 8)); + uint32 S_PixelLookupBR2 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupBR, 8)); + uint32 S_PixelsTL2 = *(uint32 *)(TexPTR + S_PixelLookupTL2*4); + uint32 S_PixelsTR2 = *(uint32 *)(TexPTR + S_PixelLookupTR2*4); + uint32 S_PixelsBL2 = *(uint32 *)(TexPTR + S_PixelLookupBL2*4); + uint32 S_PixelsBR2 = *(uint32 *)(TexPTR + S_PixelLookupBR2*4); + + uint32 S_PixelLookupTL3 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupTL, 12)); + uint32 S_PixelLookupTR3 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupTR, 12)); + uint32 S_PixelLookupBL3 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupBL, 12)); + uint32 S_PixelLookupBR3 = _mm_cvtsi128_si32(_mm_srli_si128(PixelLookupBR, 12)); + uint32 S_PixelsTL3 = *(uint32 *)(TexPTR + S_PixelLookupTL3*4); + uint32 S_PixelsTR3 = *(uint32 *)(TexPTR + S_PixelLookupTR3*4); + uint32 S_PixelsBL3 = *(uint32 *)(TexPTR + S_PixelLookupBL3*4); + uint32 S_PixelsBR3 = *(uint32 *)(TexPTR + S_PixelLookupBR3*4); + + __m128i PixelsTL = _mm_setr_epi32(S_PixelsTL0, S_PixelsTL1, S_PixelsTL2, S_PixelsTL3); + __m128i PixelsTR = _mm_setr_epi32(S_PixelsTR0, S_PixelsTR1, S_PixelsTR2, S_PixelsTR3); + __m128i PixelsBL = _mm_setr_epi32(S_PixelsBL0, S_PixelsBL1, S_PixelsBL2, S_PixelsBL3); + __m128i PixelsBR = _mm_setr_epi32(S_PixelsBR0, S_PixelsBR1, S_PixelsBR2, S_PixelsBR3); + + __m128 R_TexTL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128( PixelsTL, FF)), Norm255); + __m128 G_TexTL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsTL, 8), FF)), Norm255); + __m128 B_TexTL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsTL, 16), FF)), Norm255); + __m128 A_TexTL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsTL, 24), FF)), Norm255); + + __m128 R_TexTR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128( PixelsTR, FF)), Norm255); + __m128 G_TexTR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsTR, 8), FF)), Norm255); + __m128 B_TexTR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsTR, 16), FF)), Norm255); + __m128 A_TexTR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsTR, 24), FF)), Norm255); + + __m128 R_TexBL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128( PixelsBL, FF)), Norm255); + __m128 G_TexBL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsBL, 8), FF)), Norm255); + __m128 B_TexBL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsBL, 16), FF)), Norm255); + __m128 A_TexBL = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsBL, 24), FF)), Norm255); + + __m128 R_TexBR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128( PixelsBR, FF)), Norm255); + __m128 G_TexBR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsBR, 8), FF)), Norm255); + __m128 B_TexBR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsBR, 16), FF)), Norm255); + __m128 A_TexBR = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(PixelsBR, 24), FF)), Norm255); + + __m128 R_Col = _mm_add_ps(_mm_add_ps(_mm_mul_ps(TexBothInv, R_TexTL), + _mm_mul_ps(TexBothYInv, R_TexTR)), + _mm_add_ps(_mm_mul_ps(TexBothXInv, R_TexBL), + _mm_mul_ps(TexBoth, R_TexBR))); + __m128 G_Col = _mm_add_ps(_mm_add_ps(_mm_mul_ps(TexBothInv, G_TexTL), + _mm_mul_ps(TexBothYInv, G_TexTR)), + _mm_add_ps(_mm_mul_ps(TexBothXInv, G_TexBL), + _mm_mul_ps(TexBoth, G_TexBR))); + __m128 B_Col = _mm_add_ps(_mm_add_ps(_mm_mul_ps(TexBothInv, B_TexTL), + _mm_mul_ps(TexBothYInv, B_TexTR)), + _mm_add_ps(_mm_mul_ps(TexBothXInv, B_TexBL), + _mm_mul_ps(TexBoth, B_TexBR))); + __m128 A_Col = _mm_add_ps(_mm_add_ps(_mm_mul_ps(TexBothInv, A_TexTL), + _mm_mul_ps(TexBothYInv, A_TexTR)), + _mm_add_ps(_mm_mul_ps(TexBothXInv, A_TexBL), + _mm_mul_ps(TexBoth, A_TexBR))); + + + __m128i R_Out, G_Out, B_Out, A_Out; + + __m128 LayerAlpha = _mm_mul_ps(A_Col, LayerOpacity); + __m128 LayerAlphaInv = _mm_sub_ps(One, LayerAlpha); + + __m128 R_Colx2 = _mm_mul_ps(R_Col, Two); + __m128 R_ColInv = _mm_sub_ps(One, R_Col); + + __m128 G_Colx2 = _mm_mul_ps(G_Col, Two); + __m128 G_ColInv = _mm_sub_ps(One, G_Col); + + __m128 B_Colx2 = _mm_mul_ps(B_Col, Two); + __m128 B_ColInv = _mm_sub_ps(One, B_Col); + + __m128 R_Blend = R_Col; + __m128 G_Blend = G_Col; + __m128 B_Blend = B_Col; + __m128 A_Blend = LayerAlpha; + + if (!_mm_movemask_epi8(_mm_cvtps_epi32(_mm_cmpeq_ps(LayerAlpha, One))) || T.BlendMode != blend_normal) + { + __m128i DestPixel = _mm_loadu_si128((const __m128i *)Pixel); + __m128 R_Dest = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128( DestPixel, FF)), Norm255); + __m128 G_Dest = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(DestPixel, 8), FF)), Norm255); + __m128 B_Dest = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(DestPixel, 16), FF)), Norm255); + __m128 A_Dest = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(_mm_srli_epi32(DestPixel, 24), FF)), Norm255); + + switch (T.BlendMode) + { + case blend_normal: + { + } break; + case blend_multiply: + { + R_Blend = _mm_mul_ps(R_Dest, R_Col); + G_Blend = _mm_mul_ps(G_Dest, G_Col); + B_Blend = _mm_mul_ps(B_Dest, B_Col); + } break; + case blend_colorburn: + { + // NOTE(fox): A small amount is added to Col since images with zero for alpha may also zero out the + // color channels, causing black clipping. + R_Blend = _mm_sub_ps(One, _mm_div_ps(_mm_sub_ps(One, R_Dest), _mm_add_ps(R_Col, ClipPrevent))); + G_Blend = _mm_sub_ps(One, _mm_div_ps(_mm_sub_ps(One, G_Dest), _mm_add_ps(G_Col, ClipPrevent))); + B_Blend = _mm_sub_ps(One, _mm_div_ps(_mm_sub_ps(One, B_Dest), _mm_add_ps(B_Col, ClipPrevent))); + } break; + case blend_linearburn: + { + R_Blend = _mm_sub_ps(_mm_add_ps(R_Dest, R_Col), One); + G_Blend = _mm_sub_ps(_mm_add_ps(G_Dest, G_Col), One); + B_Blend = _mm_sub_ps(_mm_add_ps(B_Dest, B_Col), One); + } break; + case blend_add: + { + R_Blend = _mm_add_ps(R_Dest, R_Col); + G_Blend = _mm_add_ps(G_Dest, G_Col); + B_Blend = _mm_add_ps(B_Dest, B_Col); + } break; + case blend_screen: + { + R_Blend = _mm_sub_ps(One, _mm_mul_ps(_mm_sub_ps(One, R_Dest), R_ColInv)); + G_Blend = _mm_sub_ps(One, _mm_mul_ps(_mm_sub_ps(One, G_Dest), G_ColInv)); + B_Blend = _mm_sub_ps(One, _mm_mul_ps(_mm_sub_ps(One, B_Dest), B_ColInv)); + } break; + case blend_overlay: + { + __m128 R_Mask = _mm_cmp_ps(R_Dest, ZeroPointFive, 1); + __m128 G_Mask = _mm_cmp_ps(G_Dest, ZeroPointFive, 1); + __m128 B_Mask = _mm_cmp_ps(B_Dest, ZeroPointFive, 1); + __m128 R_Lower = _mm_mul_ps(Two, _mm_mul_ps(R_Dest, R_Col)); + __m128 G_Lower = _mm_mul_ps(Two, _mm_mul_ps(G_Dest, G_Col)); + __m128 B_Lower = _mm_mul_ps(Two, _mm_mul_ps(B_Dest, B_Col)); + __m128 R_Upper = _mm_sub_ps(One, _mm_mul_ps(Two, _mm_mul_ps(_mm_sub_ps(One, R_Dest), R_ColInv))); + __m128 G_Upper = _mm_sub_ps(One, _mm_mul_ps(Two, _mm_mul_ps(_mm_sub_ps(One, G_Dest), G_ColInv))); + __m128 B_Upper = _mm_sub_ps(One, _mm_mul_ps(Two, _mm_mul_ps(_mm_sub_ps(One, B_Dest), B_ColInv))); + R_Blend = _mm_blendv_ps(R_Upper, R_Lower, R_Mask); + G_Blend = _mm_blendv_ps(G_Upper, G_Lower, G_Mask); + B_Blend = _mm_blendv_ps(B_Upper, B_Lower, B_Mask); + } break; + case blend_softlight: + { + // using Pegtop's equation + R_Blend = _mm_add_ps(_mm_mul_ps(_mm_sub_ps(One, R_Colx2), _mm_mul_ps(R_Dest, R_Dest)), _mm_mul_ps(R_Colx2, R_Dest)); + G_Blend = _mm_add_ps(_mm_mul_ps(_mm_sub_ps(One, G_Colx2), _mm_mul_ps(G_Dest, G_Dest)), _mm_mul_ps(G_Colx2, G_Dest)); + B_Blend = _mm_add_ps(_mm_mul_ps(_mm_sub_ps(One, B_Colx2), _mm_mul_ps(B_Dest, B_Dest)), _mm_mul_ps(B_Colx2, B_Dest)); + } break; + case blend_hardlight: + { + __m128 R_Mask = _mm_cmp_ps(R_Dest, ZeroPointFive, 13); + __m128 G_Mask = _mm_cmp_ps(G_Dest, ZeroPointFive, 13); + __m128 B_Mask = _mm_cmp_ps(B_Dest, ZeroPointFive, 13); + __m128 R_Lower = _mm_mul_ps(Two, _mm_mul_ps(R_Dest, R_Col)); + __m128 G_Lower = _mm_mul_ps(Two, _mm_mul_ps(G_Dest, G_Col)); + __m128 B_Lower = _mm_mul_ps(Two, _mm_mul_ps(B_Dest, B_Col)); + __m128 R_Upper = _mm_sub_ps(One, _mm_mul_ps(Two, _mm_mul_ps(_mm_sub_ps(One, R_Dest), R_ColInv))); + __m128 G_Upper = _mm_sub_ps(One, _mm_mul_ps(Two, _mm_mul_ps(_mm_sub_ps(One, G_Dest), G_ColInv))); + __m128 B_Upper = _mm_sub_ps(One, _mm_mul_ps(Two, _mm_mul_ps(_mm_sub_ps(One, B_Dest), B_ColInv))); + R_Blend = _mm_blendv_ps(R_Upper, R_Lower, R_Mask); + G_Blend = _mm_blendv_ps(G_Upper, G_Lower, G_Mask); + B_Blend = _mm_blendv_ps(B_Upper, B_Lower, B_Mask); + } break; + case blend_subtract: + { + R_Blend = _mm_sub_ps(R_Dest, R_Col); + G_Blend = _mm_sub_ps(G_Dest, G_Col); + B_Blend = _mm_sub_ps(B_Dest, B_Col); + } break; + case blend_divide: + { + R_Blend = _mm_div_ps(R_Dest, _mm_add_ps(R_Col, ClipPrevent)); + G_Blend = _mm_div_ps(G_Dest, _mm_add_ps(G_Col, ClipPrevent)); + B_Blend = _mm_div_ps(B_Dest, _mm_add_ps(B_Col, ClipPrevent)); + } break; + case blend_difference: + { + __m128 R_Lower = _mm_sub_ps(R_Col, R_Dest); + __m128 G_Lower = _mm_sub_ps(G_Col, G_Dest); + __m128 B_Lower = _mm_sub_ps(B_Col, B_Dest); + __m128 R_Upper = _mm_sub_ps(R_Dest, R_Col); + __m128 G_Upper = _mm_sub_ps(G_Dest, G_Col); + __m128 B_Upper = _mm_sub_ps(B_Dest, B_Col); + __m128 R_Mask = _mm_cmp_ps(R_Lower, Zero, 14); + __m128 G_Mask = _mm_cmp_ps(G_Lower, Zero, 14); + __m128 B_Mask = _mm_cmp_ps(B_Lower, Zero, 14); + R_Blend = _mm_blendv_ps(R_Upper, R_Lower, R_Mask); + G_Blend = _mm_blendv_ps(G_Upper, G_Lower, G_Mask); + B_Blend = _mm_blendv_ps(B_Upper, B_Lower, B_Mask); + } break; + } + + R_Blend = _mm_add_ps(_mm_mul_ps(R_Dest, LayerAlphaInv), _mm_mul_ps(R_Blend, LayerAlpha)); + G_Blend = _mm_add_ps(_mm_mul_ps(G_Dest, LayerAlphaInv), _mm_mul_ps(G_Blend, LayerAlpha)); + B_Blend = _mm_add_ps(_mm_mul_ps(B_Dest, LayerAlphaInv), _mm_mul_ps(B_Blend, LayerAlpha)); + + // Standard behavior in photo apps is for blend modes to + // inherit underlying opacity instead of adding to it. + if (T.BlendMode == blend_normal) + A_Blend = _mm_add_ps(A_Dest, LayerAlpha); + else + A_Blend = A_Dest; + } + + R_Out = _mm_cvtps_epi32(_mm_mul_ps(_mm_max_ps(_mm_min_ps(One, R_Blend), Zero), Reg255)); + G_Out = _mm_cvtps_epi32(_mm_mul_ps(_mm_max_ps(_mm_min_ps(One, G_Blend), Zero), Reg255)); + B_Out = _mm_cvtps_epi32(_mm_mul_ps(_mm_max_ps(_mm_min_ps(One, B_Blend), Zero), Reg255)); + A_Out = _mm_cvtps_epi32(_mm_mul_ps(_mm_max_ps(_mm_min_ps(One, A_Blend), Zero), Reg255)); + + __m128i OutputPixel = _mm_or_si128( + _mm_or_si128(R_Out, _mm_slli_epi32(G_Out, 8)), + _mm_or_si128(_mm_slli_epi32(B_Out, 16), _mm_slli_epi32(A_Out, 24))); + _mm_maskmoveu_si128(OutputPixel, LayerMask, (char *)Pixel); + } + PixelX = _mm_add_ps(PixelX, Four); + } + } +} + +#endif +#endif diff --git a/src/sorted.cpp b/src/sorted.cpp new file mode 100644 index 0000000..394c82f --- /dev/null +++ b/src/sorted.cpp @@ -0,0 +1,336 @@ + +inline sorted_property_array * +Property_GetSortedInfo(sorted_property_array *SortedPropertyStart, int i, int h) +{ + Assert(0); + return SortedPropertyStart + (i * 8) + h; +} +inline uint16 * +Property_GetSortedArray(uint16 *SortedKeyframeArray, int i, int h) +{ + Assert(0); + return SortedKeyframeArray + (i * 8 * MAX_KEYFRAMES_PER_BLOCK) + (h * MAX_KEYFRAMES_PER_BLOCK); +} + +static sorted_layer_array * +Sorted_GetLayerStart(sorted_layer_array *LayerArrayStart, sorted_comp_array *SortedCompStart, uint32 TargetComp) +{ + uint32 LayerOffset = 0; int s = 0; + while (s < TargetComp) { + LayerOffset += SortedCompStart[s].LayerCount; + s++; + } + return LayerArrayStart + LayerOffset; +} + +// The sorting algorithm is straightforward: read every point, evaluate it if +// it's currently being interacted with by the user, record index in a sorted +// list, and repeat, shiftig the list as necessary. + +void Property_SortAll(memory *Memory, project_state *State, property_channel *Property, sorted_property_array *PropertyStart, uint16 *PropertyArrayStart) +{ + int h = 0, c = 0, i = 0; + uint32 CurrentSortIndex = 0; + real32 MinY = 1000000; + real32 MaxY = -1000000; + int32 Offset = (State->Interact_Active == interact_type_keyframe_move) ? (int32)State->Interact_Offset[0] : 0; + while (Block_Loop(Memory, Property, Property->Keyframe_Count, &h, &c, &i)) { + v2 PointPos[3]; + bezier_point *PointAddress = Bezier_LookupAddress(Memory, Property, i); + + Bezier_Interact_Evaluate(State, PointAddress, PointPos); + + if (MinY > PointAddress->Pos[0].y) { + MinY = PointAddress->Pos[0].y; + PropertyStart->MinYIndex = i; + } + if (MaxY < PointAddress->Pos[0].y) { + MaxY = PointAddress->Pos[0].y; + PropertyStart->MaxYIndex = i; + } + + uint32 SortedIndex_Playhead = 0; + while (SortedIndex_Playhead < CurrentSortIndex) { + uint16 TestPointEntry = PropertyArrayStart[SortedIndex_Playhead]; + v2 TestPointPos[3]; + bezier_point *TestPointAddress = Bezier_LookupAddress(Memory, Property, TestPointEntry); + Bezier_Interact_Evaluate(State, TestPointAddress, TestPointPos); + if (PointPos[0].x < TestPointPos[0].x ) { + break; + } else { + SortedIndex_Playhead++; + } + } + if (SortedIndex_Playhead != CurrentSortIndex) { + uint8 *Address_Start = (uint8 *)(PropertyArrayStart + SortedIndex_Playhead); + uint8 *Address_End = (uint8 *)(PropertyArrayStart + CurrentSortIndex) - 1; + Assert(CurrentSortIndex != Property->Keyframe_Count); + Arbitrary_ShiftData(Address_Start, Address_End, sizeof(uint16), 1); + } + uint16 *PointEntry = PropertyArrayStart + SortedIndex_Playhead; + *PointEntry = i; + CurrentSortIndex++; + } +} + + +static void +Layer_Sort_CheckPrev(memory *Memory, int i, int Direction, sorted_layer_array *SortedLayerStart, sorted_comp_array SortedCompStart, int *EntriesPassed, sorted_layer_array *LayerEntry, bool32 AltMethod) +{ + int PrevOffsetIndex = i + Direction + (*EntriesPassed * Direction); + bool32 OutOfBounds = (Direction > 0) ? (PrevOffsetIndex > (SortedCompStart.LayerCount - 1)) : (PrevOffsetIndex < 0); + if (!OutOfBounds) { + sorted_layer_array *PrevLayerEntry = &SortedLayerStart[PrevOffsetIndex]; + real32 PrevOffset = PrevLayerEntry->SortedOffset; + if (PrevOffset == (LayerEntry->SortedOffset - Direction)) { + (*EntriesPassed)++; + if (!AltMethod) { + PrevLayerEntry->SortedOffset += Direction; + } else { + LayerEntry->SortedOffset -= Direction; + Layer_Sort_CheckPrev(Memory, i, Direction, SortedLayerStart, SortedCompStart, EntriesPassed, LayerEntry, AltMethod); + } + } + } +} + + +// The first loop is for counting how many layers are in each precomp, the +// second is for sorting the layers by offset, and the third is for applying +// interactivity if the user is moving any layers. + +static void +Layer_SortAll(project_state *State, memory *Memory, + sorted_layer_array *LayerArrayStart, sorted_comp_array *CompArrayStart, + uint32 LayerCount, uint32 CompCount) +{ + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, LayerCount, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + Assert(Layer->Block_Composition_Index < CompCount); + CompArrayStart[Layer->Block_Composition_Index].LayerCount++; + } + h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, LayerCount, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + sorted_comp_array *SortedCompStart = &CompArrayStart[Layer->Block_Composition_Index]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(LayerArrayStart, CompArrayStart, Layer->Block_Composition_Index); + uint32 SortedIndex_Playhead = 0; + while (SortedIndex_Playhead < SortedCompStart->CurrentSortIndex) { + sorted_layer_array LayerEntry = SortedLayerStart[SortedIndex_Playhead]; + block_layer *TestLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, LayerEntry.Block_Layer_Index); + if (-Layer->Vertical_Offset < -TestLayer->Vertical_Offset) { + break; + } else { + SortedIndex_Playhead++; + } + } + if (SortedIndex_Playhead != SortedCompStart->CurrentSortIndex) { + uint8 *Address_Start = (uint8 *)(SortedLayerStart + SortedIndex_Playhead); + uint8 *Address_End = (uint8 *)(SortedLayerStart + SortedCompStart->CurrentSortIndex) - 1; + Assert(SortedCompStart->CurrentSortIndex != SortedCompStart->LayerCount); + Arbitrary_ShiftData(Address_Start, Address_End, sizeof(sorted_layer_array), 1); + } + sorted_layer_array *LayerEntry = SortedLayerStart + SortedIndex_Playhead; + LayerEntry->Block_Layer_Index = i; + LayerEntry->SortedOffset = Layer->Vertical_Offset; + SortedCompStart->CurrentSortIndex++; + } + if (State->Interact_Active == interact_type_layer_move) { + int32 Offset = (int32)State->Interact_Offset[1]; + bool32 Direction = (Offset > 0) ? 1 : -1; + for (uint32 c = 0; c < CompCount; c++) { + sorted_comp_array *SortedCompStart = &CompArrayStart[c]; + if (!SortedCompStart->LayerCount) + continue; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(LayerArrayStart, CompArrayStart, c); + int i = (Direction > 0) ? SortedCompStart->LayerCount - 1 : 0; + bool32 Case = 1; + while (Case) { + int32 EntriesPassed = 0; + sorted_layer_array *LayerEntry = &SortedLayerStart[i]; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, LayerEntry->Block_Layer_Index); + Assert(LayerEntry->SortedOffset == Layer->Vertical_Offset); + if (Layer->IsSelected) { + int32 SpacesToMove = Offset * Direction; + while (SpacesToMove) { + Layer_Sort_CheckPrev(Memory, i, Direction, SortedLayerStart, *SortedCompStart, &EntriesPassed, LayerEntry, 0); + LayerEntry->SortedOffset -= Direction; + SpacesToMove--; + } + } + int b = 0; + while (b < EntriesPassed) { + sorted_layer_array *FrontEntry = &SortedLayerStart[i+(b*Direction)]; + sorted_layer_array *BackEntry = &SortedLayerStart[i+((b+1)*Direction)]; + sorted_layer_array Swap = *FrontEntry; + *FrontEntry = *BackEntry; + *BackEntry = Swap; + b++; + } + i -= Direction; + Case = (Direction > 0) ? (i >= 0) : (i < SortedCompStart->LayerCount); + } + } + } +} + +// NOTE(fox): We could be slightly more efficient and just allocate redundant data +// instead of having another loop. +void LayerProperty_Count(project_data *File, project_state *State, memory *Memory, sorted_layer_array *LayerArrayStart, + sorted_comp_array *CompStart, uint32 LayerCount, uint32 CompCount, + uint32 *TotalPropertyCount, uint32 *TotalKeyframeCount) +{ + uint32 SortedPropertyPlayhead = 0; + uint32 SortedKeyframePlayhead = 0; + for (int c = 0; c < CompCount; c++) { + sorted_comp_array SortedCompStart = CompStart[c]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(LayerArrayStart, CompStart, c); + for (int i = 0; i < SortedCompStart.LayerCount; i++) { + sorted_layer_array *SortedLayer = &SortedLayerStart[i]; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, SortedLayer->Block_Layer_Index); + SortedLayer->SortedPropertyStart = SortedPropertyPlayhead; + SortedLayer->SortedKeyframeStart = SortedKeyframePlayhead; + for (int h = 0; h < AmountOf(Layer->Property); h++) { + property_channel *Property = &Layer->Property[h]; + if (Property->Keyframe_Count) { + SortedKeyframePlayhead += Property->Keyframe_Count; + } + SortedPropertyPlayhead++; + } + for (int i = 0; i < Layer->Block_Effect_Count; i++) { + block_effect Effect = *(block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, Layer->Block_Effect_Index[i]); + header_effect *EffectHeader = Effect_EntryFromID(State, Effect.ID); + for (int h = 0; h < EffectHeader->Property_Count; h++) { + header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + h]; + property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[h]); + if (Property->Keyframe_Count) { + SortedKeyframePlayhead += Property->Keyframe_Count; + } + SortedPropertyPlayhead++; + } + } + } + } + *TotalPropertyCount = SortedPropertyPlayhead; + *TotalKeyframeCount = SortedKeyframePlayhead; +} + +void LayerProperty_SortAll(project_data *File, project_state *State, memory *Memory, sorted_layer_array *LayerArrayStart, + sorted_comp_array *CompStart, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray, + uint32 LayerCount, uint32 CompCount) +{ + uint32 SortedPropertyPlayhead = 0; + uint32 SortedKeyframePlayhead = 0; + for (int c = 0; c < CompCount; c++) { + sorted_comp_array SortedCompStart = CompStart[c]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(LayerArrayStart, CompStart, c); + for (int i = 0; i < SortedCompStart.LayerCount; i++) { + sorted_layer_array *SortedLayer = &SortedLayerStart[i]; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, SortedLayer->Block_Layer_Index); + SortedLayer->SortedPropertyStart = SortedPropertyPlayhead; + SortedLayer->SortedKeyframeStart = SortedKeyframePlayhead; + for (int h = 0; h < AmountOf(Layer->Property); h++) { + property_channel *Property = &Layer->Property[h]; + if (Property->Keyframe_Count) { + sorted_property_array *InfoLocation = SortedPropertyStart + SortedPropertyPlayhead; + uint16 *ArrayLocation = SortedKeyframeArray + SortedKeyframePlayhead; + Property_SortAll(Memory, State, Property, InfoLocation, ArrayLocation); + SortedKeyframePlayhead += Property->Keyframe_Count; + } + SortedPropertyPlayhead++; + } + for (int i = 0; i < Layer->Block_Effect_Count; i++) { + block_effect Effect = *(block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, Layer->Block_Effect_Index[i]); + header_effect *EffectHeader = Effect_EntryFromID(State, Effect.ID); + for (int h = 0; h < EffectHeader->Property_Count; h++) { + header_property ChannelHeader = State->Property[EffectHeader->PropertyStartIndex + h]; + property_channel *Property = (property_channel *)Memory_Block_AddressAtIndex(Memory, F_Properties, Effect.Block_Property_Index[h]); + if (Property->Keyframe_Count) { + sorted_property_array *InfoLocation = SortedPropertyStart + SortedPropertyPlayhead; + uint16 *ArrayLocation = SortedKeyframeArray + SortedKeyframePlayhead; + Property_SortAll(Memory, State, Property, InfoLocation, ArrayLocation); + SortedKeyframePlayhead += Property->Keyframe_Count; + } + SortedPropertyPlayhead++; + } + } + } + } +} + +static void +TempSource_SortAll(project_data *File, project_state *State, memory *Memory, uint16 *SourceArrayStart, uint16 *TempSourceCount) +{ + int count = 0; + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Sources, File->Source_Count, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + if (Source->Type == source_type_principal_temp) { + uint32 Playhead = 0; + while (Playhead < count) { + block_source *TestSource = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, SourceArrayStart[Playhead]); + Assert(TestSource->Type == source_type_principal_temp); + if (TestSource->RelativeTimestamp > Source->RelativeTimestamp) { + break; + } else { + Playhead++; + } + } + if (Playhead != count) { + uint8 *Address_Start = (uint8 *)(SourceArrayStart + Playhead); + uint8 *Address_End = (uint8 *)(SourceArrayStart + count) - 1; + Arbitrary_ShiftData(Address_Start, Address_End, sizeof(uint16), 1); + } + SourceArrayStart[Playhead] = i; + count++; + } + } + *TempSourceCount = count; +} + + +static sorted_file +File_Sort_Push(project_data *File, project_state *State, memory *Memory) +{ + sorted_file Sorted = {0}; + Sorted.Layer_SortSize = (sizeof(sorted_comp_array) * File->Comp_Count) + + (sizeof(sorted_layer_array) * File->Layer_Count); + void *Layer_SortedArray = Memory_PushScratch(Memory, Sorted.Layer_SortSize); + Arbitrary_Zero((uint8 *)Layer_SortedArray, Sorted.Layer_SortSize); + Sorted.CompArray = (sorted_comp_array *)Layer_SortedArray; + Sorted.LayerArray = (sorted_layer_array *)((uint8 *)Layer_SortedArray + (sizeof(sorted_comp_array) * File->Comp_Count)); + + uint64 SourceArraySize = sizeof(uint16) * File->Source_Count; + Sorted.Source_SortSize = SourceArraySize; + void *Source_SortedArray = Memory_PushScratch(Memory, Sorted.Source_SortSize); + Arbitrary_Zero((uint8 *)Source_SortedArray, Sorted.Source_SortSize); + Sorted.SourceArray = (uint16 *)Source_SortedArray; + + TempSource_SortAll(File, State, Memory, Sorted.SourceArray, &Sorted.TempSourceCount); + Layer_SortAll(State, Memory, Sorted.LayerArray, Sorted.CompArray, File->Layer_Count, File->Comp_Count); + + // Property sorting depends on above precomp/layer sorting! + + uint32 TotalPropertyCount = 0; + uint32 TotalKeyframeCount = 0; + LayerProperty_Count(File, State, Memory, Sorted.LayerArray, Sorted.CompArray, File->Layer_Count, File->Comp_Count, &TotalPropertyCount, &TotalKeyframeCount); + uint64 PropertyStartSize = TotalPropertyCount * sizeof(sorted_property_array); + uint64 PropertyArraySize = TotalKeyframeCount * sizeof(uint16); + Sorted.Property_SortSize = PropertyArraySize + PropertyStartSize; + void *Property_SortedArray = Memory_PushScratch(Memory, Sorted.Property_SortSize); + Arbitrary_Zero((uint8 *)Property_SortedArray, Sorted.Property_SortSize); + Sorted.PropertyStart = (sorted_property_array *)Property_SortedArray; + Sorted.PropertyArray = (uint16 *)((uint8 *)Property_SortedArray + PropertyStartSize); + + LayerProperty_SortAll(File, State, Memory, Sorted.LayerArray, Sorted.CompArray, Sorted.PropertyStart, Sorted.PropertyArray, File->Layer_Count, File->Comp_Count); + return Sorted; +} + +static void +File_Sort_Pop(memory *Memory, uint64 Layer_SortSize, uint64 Property_SortSize, uint64 Source_SortSize) +{ + Memory_PopScratch(Memory, Property_SortSize); + Memory_PopScratch(Memory, Source_SortSize); + Memory_PopScratch(Memory, Layer_SortSize); +} diff --git a/src/stable_diffusion.cpp b/src/stable_diffusion.cpp new file mode 100644 index 0000000..4da327d --- /dev/null +++ b/src/stable_diffusion.cpp @@ -0,0 +1,287 @@ +struct curl_data { + char *response; + size_t size; +}; + +static size_t dumbcurlcallback(void *data, size_t size, size_t nmemb, void *userp) + { + size_t realsize = size * nmemb; + curl_data *mem = (curl_data *)userp; + + Memory_Copy((uint8 *)&(mem->response[mem->size]), (uint8 *)data, realsize); + mem->size += realsize; + mem->response[mem->size] = 0; + + return realsize; + } + +static void +SD_JSONToSource(project_data *File, project_state *State, memory *Memory, void *JSONResponse, int Height, int Width) +{ + // We've gotta go from base64 to compressed PNG to the raw format we want. + uint8 *Data = (uint8 *)JSONResponse; + uint64 i = 0; + Assert(Data[2] != 'd'); + while (Data[i] != '[') + i++; + i += 2; + Assert(Data[i] != '"'); + uint64 UncompressedSize = Width * Height * 4; + uint64 c = 0; + while (Data[i+c] != ']') + c++; + uint64 MaxSize = Width * Height * 4; + void *PNGData = Memory_PushScratch(Memory, MaxSize); + uint64 PNGSize = 0; + base64_decode(&Data[i], c, (uint8 *)PNGData, (size_t *)&PNGSize); + int x, y, a; + void *RawData = stbi_load_from_memory((stbi_uc *)PNGData, PNGSize, &x, &y, &a, 4); + Assert(x == Width && y == Height); + Memory_PopScratch(Memory, MaxSize); + int32 Highest = 0; + { + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Sources, File->Source_Count, &h, &c, &i)) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, i); + if (Source->Type == source_type_principal_temp && Source->RelativeTimestamp > Highest) + Highest = Source->RelativeTimestamp; + } + } + int SrcIdx = Source_Generate_Blank(File, State, Memory, Width, Height, 4); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, SrcIdx); + Source->Type = source_type_principal_temp; + Source->RelativeTimestamp = Highest + 1; + void *BitmapAddress = Memory_Block_AddressAtIndex(Memory, F_PrincipalBitmaps, Source->Bitmap_Index, 0); + Memory_Copy((uint8 *)BitmapAddress, (uint8 *)RawData, MaxSize); + stbi_image_free(RawData); +} + +static void +SD_ParseProgress(project_state *State, char *JSONInfo) +{ + char P[4]; + char P2[8]; + int ProgLocation = 0; + while (!(JSONInfo[ProgLocation] == 's' && JSONInfo[ProgLocation+1] == 's')) { + ProgLocation++; + } + ProgLocation += 4; + int ETALocation = ProgLocation; + while (!(JSONInfo[ETALocation] == 'v' && JSONInfo[ETALocation+1] == 'e')) { + ETALocation++; + } + ETALocation += 4; + // Assert(JSONInfo[ProgLocation] >= '0' && JSONInfo[ProgLocation] <= '9'); + // Assert(JSONInfo[ETALocation] >= '0' && JSONInfo[ETALocation] <= '9'); + Memory_Copy((uint8 *)P, (uint8 *)&JSONInfo[12], 4); + Memory_Copy((uint8 *)P2, (uint8 *)&JSONInfo[32], 8); + real32 Percent = atof(P); + if (Percent > 0.0f) // occasionally returns negative + State->SDPercentDone = Percent; + real32 Time = atof(P2); + if (Time > 0.1f) // occasionally returns zero for some reason + State->SDTimeEstimate = Time; + // Assert(0); +} + +static char *pre = "data:image/png;base64,"; + +static void +JSON_AppendParam_String(char *String, uint64 *i, char *P1, char *P2) +{ + uint64 c = *i; + String[c++] = '"'; + int a = 0; + while(P1[a] != '\0') { + String[c++] = P1[a++]; + } + String[c++] = '"'; + String[c++] = ':'; + String[c++] = ' '; + String[c++] = '['; + String[c++] = '"'; + a = 0; + while(pre[a] != '\0') { + String[c++] = pre[a++]; + } + a = 0; + while(P2[a] != '\0') { + String[c++] = P2[a++]; + } + c--; + String[c++] = '"'; + String[c++] = ']'; + String[c++] = ','; + String[c++] = '\n'; + String[c++] = '\0'; + *i = c; +} + +static void +SD_AssembleJSON(sd_state *SD, char *JSONPayload, void *Base64Bitmap = NULL) +{ + Arbitrary_Zero((uint8 *)JSONPayload, 1024); + // char CurlCommand[1024]; + char *Test[] = { "prompt", "negative_prompt", "steps", "width", "height", "cfg_scale" }; + void *Test2[6] = { (void *)SD->Prompt, (void *)SD->NegPrompt, + (void *)&SD->Steps, (void *)&SD->Width, + (void *)&SD->Height, (void *)&SD->CFG }; + int Type[6] = { 0, 0, 1, 1, 1, 2}; + JSONPayload[0] = '{'; + JSONPayload[1] = '\n'; + JSONPayload[2] = '\0'; + uint64 i = 2; + if (SD->Mode) { + JSON_AppendParam_String(JSONPayload, &i, "init_images", (char *)Base64Bitmap); + } + for (int i = 0; i < 6; i++) { + if (Type[i] == 0) { + sprintf(JSONPayload, "%s\"%s\": \"%s\",\n", JSONPayload, Test[i], (char *)Test2[i]); + } else if (Type[i] == 1) { + sprintf(JSONPayload, "%s\"%s\": %i,\n", JSONPayload, Test[i], *(int *)Test2[i]); + } else if (Type[i] == 2) { + sprintf(JSONPayload, "%s\"%s\": %.2f,\n", JSONPayload, Test[i], *(real32 *)Test2[i]); + } else { + Assert(0); + } + } + if (SD->Mode) + sprintf(JSONPayload, "%s\"%s\": %.2f,\n", JSONPayload, "denoising_strength", SD->DenoisingStrength); + sprintf(JSONPayload, "%s%s\n", JSONPayload, "\"sampler_index\": \"DPM++ 2S a Karras\""); + sprintf(JSONPayload, "%s}\n", JSONPayload); + printf("%s\n", JSONPayload); + // sprintf(CurlCommand, "curl -X POST -H 'Content-Type: application/json' -i '%s/sdapi/v1/txt2img' --data '%s'", SD->ServerAddress, JSONPayload); + // printf("%s\n", CurlCommand); +}; + +struct curl_state +{ + CURL *curl; + CURLM *curlm; + curl_slist *list = NULL; + curl_data CurlData; +}; + +static void +Curl_Free(curl_state *Handle) +{ + curl_multi_remove_handle(Handle->curlm, Handle->curl); + curl_easy_cleanup(Handle->curl); + curl_multi_cleanup(Handle->curlm); +} + +static void +Curl_StopAll(project_state *State, curl_state *ProgHandle, curl_state *MainHandle) +{ + Curl_Free(ProgHandle); + curl_slist_free_all(ProgHandle->list); + Curl_Free(MainHandle); + State->CurlActive = 0; +} + +static int +Curl_Check(curl_state *Handle) +{ + int IsActive; + CURLMcode mc = curl_multi_perform(Handle->curlm, &IsActive); + Assert(!mc); + if (!IsActive) { + int queue = 0; + CURLMsg *msg = curl_multi_info_read(Handle->curlm, &queue); + if (msg) { + CURL *e = msg->easy_handle; + Assert(e == Handle->curl); + Assert(msg->msg == CURLMSG_DONE); + if (!msg->data.result) { + return 1; + } else if (msg->data.result == CURLE_COULDNT_CONNECT) { + return -1; + } else { + printf("curl error: %s!\n", curl_easy_strerror(msg->data.result)); + return -2; + } + } + } + return 0; +} + +static char *APIString[] = {"txt2img", "img2img" }; + +static void +Curl_GET_Init(curl_state *C, void *OutputData, char *JSONPayload, char *IP, bool32 API) +{ + C->list = curl_slist_append(C->list, "Content-Type: application/json"); + + C->curl = curl_easy_init(); + Assert(C->curl); + + char URL[512]; + sprintf(URL, "%s/sdapi/v1/%s", IP, APIString[API]); + curl_easy_setopt(C->curl, CURLOPT_URL, URL); + curl_easy_setopt(C->curl, CURLOPT_HTTPHEADER, C->list); + curl_easy_setopt(C->curl, CURLOPT_POSTFIELDS, JSONPayload); + + C->CurlData = { (char *)OutputData, 0 }; + + curl_easy_setopt(C->curl, CURLOPT_WRITEFUNCTION, dumbcurlcallback); + curl_easy_setopt(C->curl, CURLOPT_WRITEDATA, (void *)&C->CurlData); + + C->curlm = curl_multi_init(); + Assert(C->curlm); + curl_multi_add_handle(C->curlm, C->curl); + + Curl_Check(C); +} + +static void +Curl_Prog_Init(curl_state *C, void *OutputData) +{ + C->curl = curl_easy_init(); + Assert(C->curl); + + C->CurlData = { (char *)OutputData, 0 }; + + curl_easy_setopt(C->curl, CURLOPT_URL, "http://127.0.0.1:7860/sdapi/v1/progress"); + curl_easy_setopt(C->curl, CURLOPT_WRITEFUNCTION, dumbcurlcallback); + curl_easy_setopt(C->curl, CURLOPT_WRITEDATA, (void *)&C->CurlData); + + C->curlm = curl_multi_init(); + Assert(C->curlm); + curl_multi_add_handle(C->curlm, C->curl); +} + +static void +Curl_Main(project_data *File, project_state *State, memory *Memory, curl_state *MainHandle, curl_state *ProgHandle) +{ + if (State->CurlActive == -1) { + Curl_GET_Init(MainHandle, State->Dump1, State->JSONPayload, File->UI.SD.ServerAddress, File->UI.SD.Mode); + Curl_Prog_Init(ProgHandle, State->Dump2); + State->CurlActive = 1; + } else { + if (Curl_Check(MainHandle) == 1) { + SD_JSONToSource(File, State, Memory, State->Dump1, File->UI.SD.Height, File->UI.SD.Width); + Curl_StopAll(State, ProgHandle, MainHandle); + } + uint64 Time = ImGui::GetTime(); + if (Time - State->SDTimer > 0.3f) { + int Test = Curl_Check(ProgHandle); + if (Test == 1) { + SD_ParseProgress(State, (char *)State->Dump2); + curl_multi_remove_handle(ProgHandle->curlm, ProgHandle->curl); + curl_easy_reset(ProgHandle->curl); + ProgHandle->CurlData.size = 0; + curl_easy_setopt(ProgHandle->curl, CURLOPT_URL, "http://127.0.0.1:7860/sdapi/v1/progress"); + curl_easy_setopt(ProgHandle->curl, CURLOPT_WRITEFUNCTION, dumbcurlcallback); + curl_easy_setopt(ProgHandle->curl, CURLOPT_WRITEDATA, (void *)&ProgHandle->CurlData); + curl_multi_add_handle(ProgHandle->curlm, ProgHandle->curl); + } else if (Test == -1) { + PostMsg(State, "Active stable-diffusion-webui instance not found at URL."); + Curl_StopAll(State, ProgHandle, MainHandle); + } else if (Test == -2) { + PostMsg(State, "CURL error; see command line."); + Curl_StopAll(State, ProgHandle, MainHandle); + } + State->SDTimer = Time; + } + } +} diff --git a/src/strings.cpp b/src/strings.cpp new file mode 100644 index 0000000..d2acc03 --- /dev/null +++ b/src/strings.cpp @@ -0,0 +1,92 @@ +static bool32 Hacko = false; +static int32 EffectSel = -1; + +// I'm using the filter's grep functionality to sort the effects for us +// (probably severely suboptimal), so I'm just using this callback function to +// signal back to our code that tab has been pressed in the text edit. +static int +EffectConsoleCallback(ImGuiInputTextCallbackData* data) +{ + if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) + { + Hacko = true; + } + return 0; +} + +static void +CopyStrings(void *Dest, void *Data) +{ + for (int i = 0; i < STRING_SIZE; i++) + { + *((char *)Dest + i) = *((char *)Data + i); + } +} + +static uint16 +String_AddToFile(memory *Memory, char *Char) +{ + uint16 FileIndex = Memory_Block_AllocateNew(Memory, F_Strings); + block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, FileIndex, 0); + History_Action_Block_Swap(Memory, F_Strings, String); + String->Occupied = 1; + uint16 i = 0; + while (Char[i] != '\0') { + String->Char[i] = Char[i]; + i++; + } + return FileIndex; +} + +static bool32 +String_Compare(char *String1, char *String2, uint32 Length) +{ + for (int i = 0; i < Length; i++) { + if (String1[i] != String2[i]) + return 0; + } + return 1; +} + +static uint32 +String_Length(char *Char) +{ + int32 i = 0; + while (Char[i] != '\0') { + i++; + } + return i; +} + +static void +String_Copy(char *String1, char *String2, uint32 Length) +{ + int32 i = 0; + while (i < Length) { + String1[i] = String2[i]; + i++; + } +} + +static void +String_Append(char *String1, char *String2, uint32 Length) +{ + int32 i = 0; + while (String1[i] != '\0') { + String1[i] = String2[i]; + } +} + +static void +String_PathToLayerName(char *Path, char *Dest) +{ + uint32 i = 0; + uint32 LastSlash = 0; + while (Path[i] != '\0') { + if (Path[i] == '/') + LastSlash = i; + i++; + } + for (int s = 0; s < i - LastSlash; s++) + Dest[s] = Path[s + LastSlash + 1]; +} diff --git a/src/threading.cpp b/src/threading.cpp new file mode 100644 index 0000000..5a42ea7 --- /dev/null +++ b/src/threading.cpp @@ -0,0 +1,91 @@ +static void +RenderLayers(render_entry Entry); + +static bool32 +CheckQueue(uint16 Index) +{ + bool32 Result = 0; + uint32 Q = SDL_AtomicGet(&QueuedEntries); + uint32 C = SDL_AtomicGet(&CurrentEntry); + if (Q > C) + { + if (SDL_AtomicCAS(&CurrentEntry, C, C + 1)) { + // printf("C: %i START: F%i Thread %i, region X%i Y%i\n", C, Entry->FrameNumber, Index, Entry->RenderRegion.Min.x, Entry->RenderRegion.Min.y); + RenderLayers(Entries[C]); + // printf("END: F%i Thread %i, region X%i Y%i\n", Entry->FrameNumber, Index, Entry->RenderRegion.Min.x, Entry->RenderRegion.Min.y); + SDL_AtomicAdd(&CompletedEntries, 1); + Result = 1; + } + } + return Result; +} + +static int +TestThread(void *ptr) +{ + uint16 Index = *(uint16 *)ptr; + for(;;) + { + if (!CheckQueue(Index)) + { + SDL_SemWait(Semaphore); + } + } +} + +static bool32 +Threading_IsActive(render_type RenderType) +{ + int32 Threads = 16; + if (RenderType == render_type_brush) + Threads = 4; + uint32 C = SDL_AtomicGet(&CompletedEntries); + Assert(C < Threads + 1); + return (C == Threads) ? false : true; +} + +static void +Threading_BitmapOp(void *Data, void *OutputBuffer, render_type RenderType, rectangle InitialRenderRegion) +{ + int i = (RenderType != render_type_brush) ? 4 : 2; + uint16 TileWidth = (InitialRenderRegion.Max.x - InitialRenderRegion.Min.x) / i; + uint16 TileHeight = (InitialRenderRegion.Max.y - InitialRenderRegion.Min.y) / i; + + SDL_AtomicSet(&QueuedEntries, 0); + SDL_AtomicSet(&CurrentEntry, 0); + SDL_AtomicSet(&CompletedEntries, 0); + + for (int y = 0; y < i; y++) { + for (int x = 0; x < i; x++) { + // if ((x == 0 && y == 0)|| RenderType != render_type_brush) { + + rectangle RenderRegion = { TileWidth*x, TileHeight*y, + TileWidth + TileWidth*x, TileHeight + TileHeight*y }; + + if (RenderType != render_type_main) { + RenderRegion.Min.x += InitialRenderRegion.Min.x; + RenderRegion.Min.y += InitialRenderRegion.Min.y; + RenderRegion.Max.x += InitialRenderRegion.Min.x; + RenderRegion.Max.y += InitialRenderRegion.Min.y; + } + + if (RenderRegion.Max.x > InitialRenderRegion.Max.x) + RenderRegion.Max.x = InitialRenderRegion.Max.x; + if (RenderRegion.Max.y > InitialRenderRegion.Max.y) + RenderRegion.Max.y = InitialRenderRegion.Max.y; + + if (x == i-1) + RenderRegion.Max.x = InitialRenderRegion.Max.x; + if (y == i-1) + RenderRegion.Max.y = InitialRenderRegion.Max.y; + + render_entry Entry = { Data, OutputBuffer, RenderType, RenderRegion }; + + uint32 Q = SDL_AtomicGet(&QueuedEntries); + *(Entries + Q) = Entry; + SDL_AtomicAdd(&QueuedEntries, 1); + SDL_SemPost(Semaphore); + // } + } + } +} diff --git a/src/undo.cpp b/src/undo.cpp new file mode 100644 index 0000000..d55cedc --- /dev/null +++ b/src/undo.cpp @@ -0,0 +1,309 @@ +struct history_info { + uint16 ActionCount_Total; + uint16 ActionCount_EndOfPlayhead; + uint16 ActionCount_Current; + + uint64 ActionOffset_Total; + uint64 ActionOffset_EndOfPlayhead; + uint64 ActionOffset_Current; + + uint64 EntrySize_Current; +}; + +static uint64 +History_GetActionSize(history_entry_list *History, int i, uint16 ActionCount_EndOfPlayhead) +{ + history_action *Action = &History->Action[i]; + if (Action->Type == action_type_swap || Action->Type == action_type_swap_bitmap) + return Action->Size; + return 0; +} + +// Returns information on the undo tree based on three points of data: the +// total amount of entries, the location of the playhead, and whatever location +// SampleIndex is set to, which should be either one minus playhead or the playhead. + +// I wrote this with the intent of precomputing things like the tree's total +// size and the size of the current action, because calculating them in the +// Action_Redo/Undo calls got extremely confusing. + +static history_info +History_GetTreeInfo(history_entry_list *History, uint16 SampleIndex) +{ + history_info Info = {}; + + for (int i = 0; i < History->NumberOfEntries; i++) + { + history_entry *Entry = &History->Entry[i]; + Info.ActionCount_Total += Entry->NumberOfActions; + if (i < History->EntryPlayhead) + Info.ActionCount_EndOfPlayhead += Entry->NumberOfActions; + if (i < SampleIndex) + Info.ActionCount_Current += Entry->NumberOfActions; + } + + for (int i = 0; i < Info.ActionCount_Total; i++) + { + uint64 Size = History_GetActionSize(History, i, Info.ActionCount_EndOfPlayhead); + Info.ActionOffset_Total += Size; + if (i < Info.ActionCount_EndOfPlayhead) + Info.ActionOffset_EndOfPlayhead += Size; + + if (i < Info.ActionCount_Current) + Info.ActionOffset_Current += Size; + else if (i < Info.ActionCount_EndOfPlayhead) // Only increment the current size if i is between + Info.EntrySize_Current += Size; // the current count and playhead count! + } + + return Info; +} + +// This function works exactly the same whether we're in undo/redo, pretty neat. +void History_Action_DoSwapBitmap(memory *Memory, history_action *ActionPointer, uint8 *Address_HistoryTree_Start, uint8 *Address_HistoryTree_End, uint8 *Address_Data) +{ + history_action Action = *ActionPointer; + + uint64 FullSize = Action.ShiftAmount; + uint64 CompressedSize = Action.Size; + + uint32 BytesForCompressedPart = (uint32)100 * 1024 * 1024; + void *UncompressedTempBuffer = Memory_PushScratch(Memory, FullSize); + void *CompressedTempBuffer = Memory_PushScratch(Memory, BytesForCompressedPart); + + // First we decompress and swap the saved pixels with the newer ones... + Data_Decompress(Memory, Address_HistoryTree_Start, CompressedSize, UncompressedTempBuffer, FullSize); + Bitmap_SwapData((uint8 *)UncompressedTempBuffer, (uint8 *)Address_Data, FullSize, Action.Direction); + + // Then we recompress the old pixels and evaluate its size, and since the size can be different... + uint64 NewSize = Data_Compress(Memory, UncompressedTempBuffer, FullSize, CompressedTempBuffer, BytesForCompressedPart, 0); // Z_BEST_SPEED); + + // we have to shift the undo stack. + if (NewSize != CompressedSize) { + int16 ShiftDirection = (NewSize > CompressedSize) ? 1 : -1; + uint64 ShiftAmount = (NewSize > CompressedSize) ? NewSize - CompressedSize : CompressedSize - NewSize; + Arbitrary_ShiftData(Address_HistoryTree_Start + CompressedSize, Address_HistoryTree_End, ShiftAmount, ShiftDirection); + } + Memory_Copy(Address_HistoryTree_Start, (uint8 *)CompressedTempBuffer, NewSize); + + ActionPointer->Size = NewSize; + Memory_PopScratch(Memory, BytesForCompressedPart); + Memory_PopScratch(Memory, FullSize); +} + +void History_Action_Undo(memory *Memory, history_info Info, history_action *ActionPointer, uint64 Action_Offset) +{ + history_entry_list *History = &Memory->History; + + // Only swap_bitmap should touch the data! + history_action Action = *ActionPointer; + + uint8 *Address_HistoryTree_Start = (uint8 *)Memory_AddressAtOffset(Memory, P_UndoBuffer, Action_Offset); + uint8 *Address_HistoryTree_End = (uint8 *)Memory_AddressAtOffset(Memory, P_UndoBuffer, Info.ActionOffset_Total); + uint8 *Address_Data = (uint8 *)Memory_AddressAtOffset(Memory, Action.TableName, Action.ByteOffset); + + if (Action.Type == action_type_swap) { + Arbitrary_SwapData(Memory, Address_HistoryTree_Start, Address_Data, Action.Size); + } else if (Action.Type == action_type_swap_bitmap) { + History_Action_DoSwapBitmap(Memory, ActionPointer, Address_HistoryTree_Start, Address_HistoryTree_End, Address_Data); + } else if (Action.Type == action_type_shift) { + // In order to shift back we have to start the address at where the + // shifted chunk is _now_, not when we called the function. + uint64 NewByteOffset = (Action.Direction > 0) ? Action.ByteOffset + Action.ShiftAmount : Action.ByteOffset - Action.ShiftAmount; + void *Address_Start = Memory_AddressAtOffset(Memory, Action.TableName, NewByteOffset); + uint8 *Address_End = (uint8 *)Address_Start + Action.Size; + // Direction also needs to be reversed. + int16 Direction = Action.Direction * -1; + Arbitrary_ShiftData((uint8 *)Address_Start, Address_End, Action.ShiftAmount, Direction); + } else { + Assert(0); + } +} + +void History_Action_Redo(memory *Memory, history_info Info, history_action *ActionPointer, uint64 Action_Offset) { + + history_entry_list *History = &Memory->History; + + history_action Action = *ActionPointer; + + uint8 *Address_HistoryTree_Start = (uint8 *)Memory_AddressAtOffset(Memory, P_UndoBuffer, Action_Offset); + uint8 *Address_HistoryTree_End = (uint8 *)Memory_AddressAtOffset(Memory, P_UndoBuffer, Info.ActionOffset_Total); + uint8 *Address_Data = (uint8 *)Memory_AddressAtOffset(Memory, Action.TableName, Action.ByteOffset); + + if (Action.Type == action_type_swap) { + Arbitrary_SwapData(Memory, Address_HistoryTree_Start, Address_Data, Action.Size); + } else if (Action.Type == action_type_swap_bitmap) { + History_Action_DoSwapBitmap(Memory, ActionPointer, Address_HistoryTree_Start, Address_HistoryTree_End, Address_Data); + } else if (Action.Type == action_type_shift) { + void *Address_Start = Memory_AddressAtOffset(Memory, Action.TableName, Action.ByteOffset); + uint8 *Address_End = (uint8 *)Address_Start + Action.Size; + int16 Direction = Action.Direction; + Arbitrary_ShiftData((uint8 *)Address_Start, Address_End, Action.ShiftAmount, Direction); + } else { + Assert(0); + } +} + +void History_Undo(memory *Memory) { + history_entry_list *History = &Memory->History; + if (History->EntryPlayhead == 0) return; + + // We want Current to represent the beginning of the current entry, so we + // subtract one from the playhead. + history_info Info = History_GetTreeInfo(History, History->EntryPlayhead - 1); + history_entry *Entry = &History->Entry[History->EntryPlayhead - 1]; + + // This puts us at the end of the current entry's offset. + uint64 ActionOffset_Stepback = Info.ActionOffset_Current + Info.EntrySize_Current; + + for (int i = Entry->NumberOfActions - 1; i >= 0; i--) { + history_action *Action = &History->Action[Info.ActionCount_Current + i]; + + // We step backwards only if the action is currently storing data. + if (Action->Type != action_type_shift) + ActionOffset_Stepback -= Action->Size; + + History_Action_Undo(Memory, Info, Action, ActionOffset_Stepback); + } + + History->EntryPlayhead--; + +} + +void History_Redo(memory *Memory) { + history_entry_list *History = &Memory->History; + if (History->EntryPlayhead == History->NumberOfEntries) return; + + // NOTE(fox): The third part of this function for recording the "current" + // action's size is only necessary for the undo call, so it's not correct on the redo. + history_info Info = History_GetTreeInfo(History, History->EntryPlayhead); + history_entry *Entry = &History->Entry[History->EntryPlayhead]; + + History->EntryPlayhead++; + + uint64 ActionOffset_Stepforward = Info.ActionOffset_Current; + + for (int i = 0; i < Entry->NumberOfActions; i++) { + history_action *Action = &History->Action[Info.ActionCount_Current + i]; + + History_Action_Redo(Memory, Info, Action, ActionOffset_Stepforward); + + // We step forwards only if the action is currently storing data. + if (Action->Type != action_type_shift) + ActionOffset_Stepforward += Action->Size; + + } + +} + +// This is only called when we're certain the action is going to be taken. +void History_Entry_Commit(memory *Memory, char *Name) +{ + history_entry_list *History = &Memory->History; + history_entry *Entry = &History->Entry[History->EntryPlayhead]; + Entry->Name = Name; + Entry->NumberOfActions = 0; + // Effectively deletes entries in front if we're beginning out of an undo. + if (History->NumberOfEntries != History->EntryPlayhead) + History->NumberOfEntries = History->EntryPlayhead; + History->NumberOfEntries++; + History->EntryPlayhead++; + Memory->IsFileSaved = false; + Memory->PurgeCache = true; +#if DEBUG + Assert(Debug.UndoState != 1); // You forgot to end a History_Entry_Commit()! + Debug.UndoState = 1; +#endif +} + +void History_Entry_End(memory *Memory) +{ + history_entry_list *History = &Memory->History; +#if DEBUG + Debug.UndoState = 0; +#endif +} + +// NOTE(fox): Shift is the only action that additionally changes the data after +// the info is put on the tree, since it's always the same function used. + +static void History_Action_Add(memory *Memory, history_action ActionData, void *ReferenceData = NULL) +{ + history_entry_list *History = &Memory->History; + history_entry *Entry = &History->Entry[History->EntryPlayhead - 1]; + + history_info Info = History_GetTreeInfo(History, History->EntryPlayhead - 1); + + history_action *Action = &History->Action[Info.ActionCount_Total]; + *Action = ActionData; + + if (Action->Type == action_type_swap) + { + void *Address_HistoryTree = Memory_AddressAtOffset(Memory, P_UndoBuffer, Info.ActionOffset_Total); + void *Address_Data = Memory_AddressAtOffset(Memory, Action->TableName, Action->ByteOffset); + Memory_Copy((uint8 *)Address_HistoryTree, (uint8 *)Address_Data, Action->Size); + } else if (Action->Type == action_type_swap_bitmap) + { + // The address of data to be stored in the undo tree is not the same as + // the address to be recorded, so we have to pass it manually. + void *Address_HistoryTree = Memory_AddressAtOffset(Memory, P_UndoBuffer, Info.ActionOffset_Total); + void *Address_Data = ReferenceData; + + // Libz/miniz requires passing an amount of available space, and it + // returns how much space the compressed data actually is. + // We don't need to alloc any more memory since we can just use the + // history tree as the destination. + // ShiftAmount is also being used to save the uncompressed size. + uint32 BytesForCompressedPart = (uint32)100 * 1024 * 1024; + Action->Size = Data_Compress(Memory, Address_Data, Action->ShiftAmount, Address_HistoryTree, BytesForCompressedPart, 0); // Z_BEST_SPEED); + + } else if (Action->Type == action_type_shift) + { + void *Address_Start = Memory_AddressAtOffset(Memory, Action->TableName, Action->ByteOffset); + uint8 *Address_End = (uint8 *)Address_Start + Action->Size; + Arbitrary_ShiftData((uint8 *)Address_Start, Address_End, Action->ShiftAmount, Action->Direction); + } + + Entry->NumberOfActions++; +} + +// Helper functions for different tables. + +static void History_Action_BitmapPaint(memory *Memory, uint64 Size_Uncompressed, + void *SourceBitmapAddress, void *CachedPaintAddress, int16 BytesPerPixel) +{ + void *Address_Start = Memory->Slot[F_PrincipalBitmaps].Address; + uint64 ByteOffset = (ptrsize)SourceBitmapAddress - (ptrsize)Address_Start; + history_action Action = { F_PrincipalBitmaps, action_type_swap_bitmap, 0, ByteOffset, Size_Uncompressed, BytesPerPixel }; + History_Action_Add(Memory, Action, CachedPaintAddress); +} + +static void History_Action_Shift(memory *Memory, memory_table_list TableName, + void *Address0, void *Address1, uint64 Amount, int16 Direction) +{ + void *Address_Start = Memory->Slot[TableName].Address; + uint64 ByteOffset = (ptrsize)Address0 - (ptrsize)Address_Start; + uint64 Size = (ptrsize)Address1 - (ptrsize)Address0; + + history_action Action = { TableName, action_type_shift, Size, ByteOffset, Amount, Direction }; + + History_Action_Add(Memory, Action); +} + +static void History_Action_Block_Swap(memory *Memory, memory_table_list TableName, void *Address_Data) +{ + void *Address_Start = Memory->Slot[TableName].Address; + uint64 Size = Memory->Slot[TableName].Block_ElementSize; + uint64 ByteOffset = (ptrsize)Address_Data - (ptrsize)Address_Start; + history_action Action = { TableName, action_type_swap, Size, ByteOffset, 0 }; + History_Action_Add(Memory, Action); +} + +// Remember to dereference pointers if taking the sizeof() a variable, or else Size will be 8! + +static void History_Action_Swap(memory *Memory, memory_table_list TableName, uint64 Size, void *Address_Data) +{ + void *Address_Start = Memory->Slot[TableName].Address; + uint64 ByteOffset = (ptrsize)Address_Data - (ptrsize)Address_Start; + history_action Action = { TableName, action_type_swap, Size, ByteOffset, 0 }; + History_Action_Add(Memory, Action); +} |