summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bezier.cpp415
-rw-r--r--src/createcalls.cpp1168
-rw-r--r--src/effects.cpp58
-rw-r--r--src/effects_constructors.cpp117
-rw-r--r--src/effects_gl.cpp103
-rw-r--r--src/effects_gl_shader.cpp57
-rw-r--r--src/effects_software.cpp209
-rw-r--r--src/ffmpeg_backend.cpp480
-rw-r--r--src/gl_calls.cpp268
-rw-r--r--src/imgui_helper.cpp90
-rw-r--r--src/imgui_helper_internal.cpp222
-rw-r--r--src/imgui_ui.cpp1502
-rw-r--r--src/imgui_ui_debug.cpp76
-rw-r--r--src/imgui_ui_properties.cpp441
-rw-r--r--src/imgui_ui_stable_diffusion.cpp175
-rw-r--r--src/imgui_ui_timeline.cpp1070
-rw-r--r--src/include/debug.h99
-rw-r--r--src/include/defines.h52
-rw-r--r--src/include/ffmpeg_backend.h37
-rw-r--r--src/include/functions.h90
-rw-r--r--src/include/gl_calls.h24
-rw-r--r--src/include/imgui_internal_widgets.h15
-rw-r--r--src/include/imgui_ops.h106
-rw-r--r--src/include/keybinds.h205
-rw-r--r--src/include/layer.h50
-rw-r--r--src/include/main.h686
-rw-r--r--src/include/memory.h71
-rw-r--r--src/include/my_math.h762
-rw-r--r--src/include/sharebuffer.h17
-rw-r--r--src/include/stable_diffusion.h33
-rw-r--r--src/include/structs.h5
-rw-r--r--src/include/undo.h2
-rw-r--r--src/io.cpp23
-rw-r--r--src/layer.cpp405
-rw-r--r--src/main.cpp915
-rw-r--r--src/memory.cpp386
-rw-r--r--src/paint.cpp103
-rw-r--r--src/prenderer.cpp1914
-rw-r--r--src/sorted.cpp336
-rw-r--r--src/stable_diffusion.cpp287
-rw-r--r--src/strings.cpp92
-rw-r--r--src/threading.cpp91
-rw-r--r--src/undo.cpp309
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, &current);
+ 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);
+}