#if SPECIAL #include "main.h" #endif 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); File->UI.Shape = {}; 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, 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, block_composition *DupeComp) { 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); if (DupeComp) { *Comp = *DupeComp; } else { *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 & 0x01) { Layer->Block_Effect_Index[Layer->Block_Effect_Count] = Effect_Init(State, Memory, EffectEntryIndex, Layer->Block_Effect_Count); History_Action_Swap(Memory, F_Layers, 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 & 0x01)) 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 & 0x01) { 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 & 0x01)) { 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 Slide_Init(project_data *File, project_state *State, memory *Memory) { // It's impossible to use the brush while this is active, so for now we can // just use its buffer to store our data: uint8 *InteractBuffer = (uint8 *)State->Brush.TransientBitmap; State->Interact_Count = 0; 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 & 0x01) { interact_slide_layer *Interact_Layer = (interact_slide_layer *)InteractBuffer; *Interact_Layer = {0}; Interact_Layer->Index = i; InteractBuffer += sizeof(interact_slide_layer); State->Interact_Count++; } } } void Slide_Test(project_data *File, project_state *State, memory *Memory, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) { block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); v2 CompDimensions = V2(MainComp->Width, MainComp->Height); real32 Threshold = 10; uint8 *InteractBuffer = (uint8 *)State->Brush.TransientBitmap; for (int i = 0; i < State->Interact_Count; i++) { interact_slide_layer *Interact_Layer = (interact_slide_layer *)InteractBuffer; InteractBuffer += sizeof(interact_slide_layer); block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Interact_Layer->Index); v2 CompUV = V2(State->Interact_Offset[0], State->Interact_Offset[1]); v2 CompPos = CompUV * CompDimensions; // v2 LayerPos = Layer_TraverseForPoint(File, State, Memory, CompUV, SortedCompArray, SortedLayerArray, Interact_Layer->Index); v2 LayerPos = V2(Layer->x.CurrentValue, Layer->y.CurrentValue); int Width, Height; Layer_GetDimensions(Memory, Layer, &Width, &Height); v2 Difference = V2(fabs(CompPos.x - LayerPos.x), fabs(CompPos.y - LayerPos.y)); printf("Diff: %.1f, %.1f\n", Difference.x, Difference.y); if (Difference.x < Threshold && Difference.y < Threshold) Assert(0); } } 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) { 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 Layer_Nudge(project_data *File, project_state *State, memory *Memory, 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); State->Interact_Active = interact_type_layer_move; State->Interact_Offset[0] = -1; History_Entry_Commit(Memory, "Move layer stack down"); 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_Layers, sizeof(Layer->Vertical_Offset), &Layer->Vertical_Offset); Layer->Vertical_Offset = SortEntry.SortedOffset; if (Layer->IsSelected & 0x01) { History_Action_Swap(Memory, F_Layers, sizeof(Layer->Frame_Offset), &Layer->Frame_Offset); Interact_Evaluate_Layer(Memory, State, Index_Physical, SortedCompStart, SortedLayerStart, &Layer->Frame_Start, &Layer->Frame_End, &Layer->Frame_Offset); } } State->Interact_Active = interact_type_none; State->Interact_Modifier = 0; History_Entry_End(Memory); } 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, int *Frame_Offset) { if (State->Interact_Active == interact_type_layer_move) { *Frame_Offset += (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 & 0x01)) 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; } } } } 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 & 0x01) { 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; } // TODO(fox): It seems like duping many layers at once causes this to take up // exponentially more data on the undo tree than it should. Rewrite the loop to // change the offsets before duping, taking into account all dupes, like what // the sorting code does. // TODO(fox): Add different modes (all dupes on top, each dupe above its layer, two for bottom) static void Sort_OffsetDupes(memory *Memory, sorted_layer_array *SortedLayerStart, block_layer *StartLayer, int i, int FauxIncrement, int LayerCount, int Mode) { block_layer *PrevLayer = StartLayer; for (int a = i+1; a < LayerCount; a++) { sorted_layer_array *NextSortEntry = &SortedLayerStart[a]; uint32 NextIndex_Physical = NextSortEntry->Block_Layer_Index; block_layer *NextLayer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, NextIndex_Physical); if (NextLayer->Vertical_Offset == PrevLayer->Vertical_Offset) { if (Mode == 0 && !NextSortEntry->IsFake) { History_Action_Swap(Memory, F_Layers, sizeof(NextLayer->Vertical_Offset), &NextLayer->Vertical_Offset); NextLayer->Vertical_Offset -= 1; } else { NextSortEntry->SortedOffset -= 1; } } else { break; } PrevLayer = NextLayer; } } // NOTE(fox): PrecompLayer is assumed to be untouched from the layer it was duplicated from! static uint16 Layer_Precomp_CopyContents(project_data *File, project_state *State, memory *Memory, block_layer *PrecompLayer, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) { Assert(PrecompLayer->IsPrecomp); int CompIndex = PrecompLayer->Block_Source_Index; sorted_comp_array SortedCompStart = SortedCompArray[CompIndex]; sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); int LayerCount = SortedCompStart.LayerCount; Assert(SortedCompStart.FakeLayerCount == 0); block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, CompIndex); block_composition *NewPrecomp = Precomp_Init(File, Memory, Comp); uint16 NewCompIndex = Memory_Block_LazyIndexAtAddress(Memory, F_Precomps, NewPrecomp); for (int i = 0; i < LayerCount; i++) { sorted_layer_array SortEntry = SortedLayerStart[i]; uint32 Index_Physical = SortEntry.Block_Layer_Index; block_layer *NewLayer = NULL; { block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); Assert(!(Layer->IsSelected & 0x01)); NewLayer = (block_layer *)Memory_Block_AllocateAddress(Memory, F_Layers); History_Action_Block_Swap(Memory, F_Layers, NewLayer); *NewLayer = *Layer; } NewLayer->Block_Composition_Index = NewCompIndex; // TODO(fox): Effect duplication Assert(NewLayer->Block_Effect_Count == 0); block_string *OldString = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, NewLayer->Block_String_Index); NewLayer->Block_String_Index = Memory_Block_AllocateNew(Memory, F_Strings); block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, NewLayer->Block_String_Index, 0); sprintf(String->Char, "%s", OldString->Char); History_Action_Swap(Memory, F_Strings, sizeof(String->Occupied), &String->Occupied); String->Occupied = 1; History_Action_Swap(Memory, F_File, sizeof(File->Layer_Count), &File->Layer_Count); File->Layer_Count++; } return NewCompIndex; } static void Project_Layer_Duplicate(project_data *File, project_state *State, memory *Memory, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, v2 Offset, bool32 FakeOnly, bool32 NewPrecomp) { for (int c = 0; c < File->Comp_Count; c++) { sorted_comp_array SortedCompStart = SortedCompArray[c]; sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, c); int LayerCount = SortedCompStart.LayerCount + SortedCompStart.FakeLayerCount; for (int i = 0; i < 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 ((FakeOnly && SortEntry.IsFake) || (!FakeOnly && (Layer->IsSelected & 0x01))) { if (!FakeOnly) Layer->IsSelected = 0x00; block_layer *NewLayer = (block_layer *)Memory_Block_AllocateAddress(Memory, F_Layers); History_Action_Block_Swap(Memory, F_Layers, NewLayer); *NewLayer = *Layer; if (NewPrecomp && Layer->IsPrecomp) { NewLayer->Block_Source_Index = Layer_Precomp_CopyContents(File, State, Memory, NewLayer, SortedCompArray, SortedLayerArray); } // TODO(fox): Effect duplication Assert(Layer->Block_Effect_Count == 0); Layer_Select(Memory, State, Memory_Block_LazyIndexAtAddress(Memory, F_Layers, NewLayer)); NewLayer->Vertical_Offset--; Sort_OffsetDupes(Memory, SortedLayerStart, NewLayer, i, 0, LayerCount, 0); Assert(!NewLayer->x.Keyframe_Count); Assert(!NewLayer->y.Keyframe_Count); NewLayer->x.CurrentValue += Offset.x; NewLayer->y.CurrentValue += Offset.y; NewLayer->Block_String_Index = Memory_Block_AllocateNew(Memory, F_Strings); block_string *String = (block_string *)Memory_Block_AddressAtIndex(Memory, F_Strings, NewLayer->Block_String_Index, 0); sprintf(String->Char, "Layer %i", File->Layer_Count + 1); // CSbros... History_Action_Swap(Memory, F_Strings, sizeof(String->Occupied), &String->Occupied); String->Occupied = 1; History_Action_Swap(Memory, F_File, sizeof(File->Layer_Count), &File->Layer_Count); File->Layer_Count++; } else { Layer->IsSelected = 0x00; } } } } // TODO(fox): We may need a way to recalculate width/height when points are // moved since selection depends on it. // TODO(fox): We could also write better selection code for shapes that // actually tests using the bezier path (preferrable). static void Project_ShapeLayer_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); Layer_DeselectAll(File, State, Memory); History_Entry_Commit(Memory, "New shape layer"); block_layer *Layer = Layer_Init(File, Memory); Layer->IsShapeLayer = true; 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)); shape_layer *Shape = &File->UI.Shape; Layer->Block_Composition_Index = File->PrincipalCompIndex; if (State->MostRecentlySelectedLayer == -1) { Layer->Block_Composition_Index = File->PrincipalCompIndex; } else { // TODO(fox): Allow shapes to be drawn inside selected precomps? // LayerIterate_DeepestPrecomp(State, Memory, CompIndex, ExtraT, Center, // SortedCompArray, SortedLayerArray, SelectionCount, SelectedLayerIndex, SelectedPrecompIndex) } // Rect mode if (State->ShapeMode == 0) { v2 *CompPoint = (v2 *)&State->Interact_Offset[0]; for (int i = 0; i < 4; i++) { bezier_point PointData = { 1, { CompPoint[i], V2(0, 0), V2(0, 0) }, interpolation_type_linear, 0 }; Bezier_Add(Memory, F_File, Shape->Block_Bezier_Index, &Shape->Block_Bezier_Count, &Shape->Point_Count, PointData); CompPoint[i] = {0}; } Shape->IsClosed = true; } else if (State->ShapeMode == 1) { v2 Radius = *((v2 *)&State->Interact_Offset + 0); v2 Center = *((v2 *)&State->Interact_Offset + 1); real32 cx = Center.x; real32 cy = Center.y; real32 rx = Radius.x; real32 ry = Radius.y; v2 Points[12] = { V2(cx-rx, cy), V2(0, ry*KAPPA), V2(0, -ry*KAPPA), V2(cx, cy+ry), V2(+rx*KAPPA, 0), V2(-rx*KAPPA, 0), V2(cx+rx, cy), V2(0, -ry*KAPPA), V2(0, ry*KAPPA), V2(cx, cy-ry), V2(-rx*KAPPA, 0), V2(+rx*KAPPA, 0) }; for (int i = 0; i < 4; i++) { bezier_point PointData = { 1, { Points[i*3+0], Points[i*3+1], Points[i*3+2] }, interpolation_type_bezier, 0 }; Bezier_Add(Memory, F_File, Shape->Block_Bezier_Index, &Shape->Block_Bezier_Count, &Shape->Point_Count, PointData); } // Shape->IsClosed = true; } shape_options ShapeOpt = File->UI.ShapeOpt; ShapeOpt.FillCol = File->UI.Color; ShapeOpt.StrokeCol = File->UI.AltColor; v2 Min = {}, Max = {}; void *Data = Memory_PushScratch(Memory, sizeof(nvg_point) * 128); layer_transforms T = {}; bool32 IsConvex = 0; NVG_FlattenPath(Memory, Shape, ShapeOpt, (nvg_point *)Data, State, T, 0, 0, 0, 0, 0, &Min, &Max, &IsConvex); Memory_PopScratch(Memory, sizeof(nvg_point) * 128); Shape->Width = Max.x - Min.x; Shape->Height = Max.y - Min.y; for (int i = 0; i < Shape->Point_Count; i++) { bezier_point *Point = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, i, 1); for (int a = 0; a < 3; a++) { v2 Sub = (a == 0) ? Min : V2(0, 0); Point->Pos[a] = (Point->Pos[a] - Sub) / V2(Shape->Width, Shape->Height); } } Layer->x.CurrentValue = Min.x + (Shape->Width / 2); Layer->y.CurrentValue = Min.y + (Shape->Height / 2); /* v2 Min = V2(10000, 10000); v2 Max = V2(-10000, -10000); shape_layer *Shape = &File->UI.Shape; for (int i = 0; i < Shape->Point_Count; i++) { bezier_point *Point = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, i, 1); if (Point->Pos[0].x > Max.x) Max.x = Point->Pos[0].x; if (Point->Pos[0].x < Min.x) Min.x = Point->Pos[0].x; if (Point->Pos[0].y > Max.y) Max.y = Point->Pos[0].y; if (Point->Pos[0].y < Min.y) Min.y = Point->Pos[0].y; } v2 ShapeSize = (Max - Min); Layer->x.CurrentValue = Min.x + (ShapeSize.x / 2); Layer->y.CurrentValue = Min.y + (ShapeSize.y / 2); */ History_Action_Swap(Memory, F_Layers, sizeof(Layer->Shape), &Layer->Shape); Layer->Shape = File->UI.Shape; History_Action_Swap(Memory, F_Layers, sizeof(Layer->ShapeOpt), &Layer->ShapeOpt); Layer->ShapeOpt = ShapeOpt; History_Action_Swap(Memory, F_File, sizeof(File->UI.Shape), &File->UI.Shape); File->UI.Shape = {}; History_Entry_End(Memory); State->UpdateFrame = true; } 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_Sources, 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; if (Source->HasVideo) { av_info *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++; } } State->UpdateFrame = true; } if (CommitAction) History_Entry_End(Memory); Source_DeselectAll(File, Memory); } 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 & 0x01) { } } } 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 & 0x01) { 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 & 0x01) { History_Action_Swap(Memory, F_Layers, 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++; }