Something went wrong. Try again.
art software for the rest of us
drawing paint image sdl art
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525#include "CanvasLayer.h"#include <cmath>#include "../GPU.h"#include "../Assets.h"#include "../Input/InputHandler.h"#include "../App.h"#include "../AppLayers/NotifyLayer.h"#include "../ManagementContext/LayerManagmentContext.h"#include "../PrimitivesBuilder/PipelineBuilder.h"#include "../PrimitivesBuilder/VertexInputStateBuilder.h"#include "../UserInterface/Popup.h"
void CanvasAppLayer::uploadBuffers(){
SDL_GPUBufferCreateInfo buf_info = { .usage = SDL_GPU_BUFFERUSAGE_VERTEX, .size = sizeof(previewVertices), .props = {} }; previewBuffer = new GPUBuffer(&buf_info); canvasBuffer = new GPUBuffer(&buf_info); if (!previewBuffer) { throw std::runtime_error("Failed to create vertex buffer"); }
SDL_GPUSamplerCreateInfo samplerInfo = {}; samplerInfo.min_filter = SDL_GPU_FILTER_LINEAR; samplerInfo.mag_filter = SDL_GPU_FILTER_LINEAR; samplerInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; samplerInfo.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; samplerInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; samplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; samplerInfo.mip_lod_bias = 0.0f; samplerInfo.max_anisotropy = 1.0f; samplerInfo.compare_op = SDL_GPU_COMPAREOP_NEVER; samplerInfo.min_lod = 0.0f; samplerInfo.max_lod = 1000.0f;
sampler = SDL_CreateGPUSampler(GPU::GetDevice(), &samplerInfo); if (!sampler) { throw std::runtime_error(SDL_GetError()); }}
void CanvasAppLayer::setVertexBuffer(SDL_FRect fullRect, SDL_FRect previewRect){#define NDC_TO_SCREEN * 2.0f - 1.0f // parameters are using screen space // bottom left previewVertices[0] = (previewRect.x - fullRect.x) / fullRect.w NDC_TO_SCREEN; previewVertices[1] = (previewRect.y - fullRect.y) / fullRect.h NDC_TO_SCREEN; previewVertices[2] = (previewRect.x - fullRect.x) / fullRect.w; // match 0 previewVertices[3] = (previewRect.y - fullRect.y) / fullRect.h; // match 13 // bottom right previewVertices[4] = (previewRect.x + previewRect.w - fullRect.x) / fullRect.w NDC_TO_SCREEN; previewVertices[5] = (previewRect.y - fullRect.y) / fullRect.h NDC_TO_SCREEN; previewVertices[6] = (previewRect.x + previewRect.w - fullRect.x) / fullRect.w; // match 4 previewVertices[7] = (previewRect.y - fullRect.y) / fullRect.h; // match 13 // top left previewVertices[8] = (previewRect.x - fullRect.x) / fullRect.w NDC_TO_SCREEN; previewVertices[9] = (previewRect.y + previewRect.h - fullRect.y) / fullRect.h NDC_TO_SCREEN; previewVertices[10] = (previewRect.x - fullRect.x) / fullRect.w; // match 8 previewVertices[11] = (previewRect.y + previewRect.h - fullRect.y) / fullRect.h; // match 1 // top right previewVertices[12] = (previewRect.x + previewRect.w - fullRect.x) / fullRect.w NDC_TO_SCREEN; previewVertices[13] = (previewRect.y + previewRect.h - fullRect.y) / fullRect.h NDC_TO_SCREEN; previewVertices[14] = (previewRect.x + previewRect.w - fullRect.x) / fullRect.w; // match 12 previewVertices[15] = (previewRect.y + previewRect.h - fullRect.y) / fullRect.h; // match 1
// invert y for (int i = 1; i < 16; i += 4) { previewVertices[i] = -1.0f * previewVertices[i]; }#undef NDC_TO_SCREEN previewBuffer->Transfer(previewVertices);}
void CanvasAppLayer::setVertexBufferCanvas(){ SDL_FPoint corners[4] = { { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f } };
for (int i = 0; i < 4; i++) { computeVertex(corners[i].x, corners[i].y, previewVertices[i * 4], previewVertices[i * 4 + 1]); previewVertices[i * 4 + 2] = corners[i].x; // Restored your intentional UV inversion here: previewVertices[i * 4 + 3] = corners[i * -1 + 3].y; } canvasBuffer->Transfer(previewVertices);}
void CanvasAppLayer::computeVertex(float cx, float cy, float& outX, float& outY) {
// orgin float px = (cx - 0.5f) * LayerManagementContext::FinalWidth; float py = (cy - 0.5f) * LayerManagementContext::FinalHeight;
// rotation float cosR = cosf(Rotation); float sinR = sinf(Rotation); float rx = px * cosR - py * sinR; float ry = px * sinR + py * cosR;
// zoom rx *= Zoom; ry *= Zoom;
// ndc pan outX = (rx / (App::Width * 0.5f)) + (PanX * 2 / App::Width); outY = (ry / (App::Height * 0.5f)) + (PanY * 2 / App::Height);}
void CanvasAppLayer::createPipelines(){ vertShader = new Shader(Assets::Path("shaders/CanvasRenderingTarget.vert.spirv"), "main", 0, 0, 0, 0); fragShader = new Shader(Assets::Path("shaders/CanvasRenderingTarget.frag.spirv"), "main", 1, 0, 0, 1); transparencyFragShader = new Shader(Assets::Path("shaders/TransparencyField.frag.spirv"), "main", 0, 0, 0, 1);
auto vertexInputState = VertexInputStateBuilder() .AddAttribute(0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2) .AddAttribute(1, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2) .BuildDeferred();
pipelineBlend = PipelineBuilder() .Shaders(vertShader, fragShader) .FormatSwapchain() .BlendPremultiplied() .Primitive(SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP) .Vertex(vertexInputState.Use()) .Build();
pipelineLocalPreview = PipelineBuilder() .Shaders(vertShader, fragShader) .Format(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM) .BlendAlpha() .Primitive(SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP) .Vertex(vertexInputState.Use()) .Build();
pipelineLocalPreviewEraser = PipelineBuilder() .Shaders(vertShader, fragShader) .Format(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM) .BlendErase() .Primitive(SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP) .Vertex(vertexInputState.Use()) .Build();
pipelineTransparencyField = PipelineBuilder() .Shaders(vertShader, transparencyFragShader) .FormatSwapchain() .BlendPremultiplied() .Primitive(SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP) .Vertex(vertexInputState.Use()) .Build();}
#include "../LayerNodes/PaintLayerNode.h"void CanvasAppLayer::renderPreviewStage(SDL_GPUColorTargetInfo swapchainTargetInfo, SDL_GPUViewport viewport) { auto* paintLayer = dynamic_cast<PaintLayerNode*>(LayerManagementContext::SelectedLayer); if (!paintLayer) return; paintLayer->Target->CopyTo(localPreview, { 0, 0 }, { 0, 0 }, { LayerManagementContext::FinalWidth, LayerManagementContext::FinalHeight }); SDL_FRect previewBoundingBox = Tool::GetActive()->GetPreviewBoundingBox();
SDL_GPUViewport viewport2 = {}; viewport2.x = 0.0f; viewport2.y = 0.0f; viewport2.w = (float)LayerManagementContext::FinalWidth; viewport2.h = (float)LayerManagementContext::FinalHeight; viewport2.min_depth = 0.0f; viewport2.max_depth = 1.0f;
SDL_FRect viewrect = *((SDL_FRect*)&viewport2); // we love a bit of memory tomfoolery :3 // the address of the viewport, as a pointer to an SDL_FRect, dereferenced. setVertexBuffer(viewrect, previewBoundingBox);
// Blend ontop SDL_GPUColorTargetInfo previewTargetInfo = {}; previewTargetInfo.texture = localPreview->Get(); previewTargetInfo.clear_color = { 0.0f, 0.0f, 0.0f, 0.0f }; previewTargetInfo.load_op = SDL_GPU_LOADOP_LOAD; previewTargetInfo.store_op = SDL_GPU_STOREOP_STORE; previewTargetInfo.mip_level = 0; previewTargetInfo.layer_or_depth_plane = 0; previewTargetInfo.cycle = false; GPU::AquirePushCommandBuffer(); renderFinalCompositeToTarget({ &previewTargetInfo }, preview, viewport2, Tool::GetActive()->IsEraser() ? pipelineLocalPreviewEraser : pipelineLocalPreview, previewBuffer, *Tool::GetActive()->Opacity); GPU::SubmitCommandsPopCommandBuffer();
// Render replace layerRenderingContext->CompositeReplacedSelectedLayer(localPreview)->CopyTo(localPreview, { 0, 0 }, { 0, 0 }, { LayerManagementContext::FinalWidth, LayerManagementContext::FinalHeight }); setVertexBufferCanvas();
renderFinalCompositeToTarget({ &swapchainTargetInfo }, localPreview, viewport, pipelineBlend, canvasBuffer);}
void CanvasAppLayer::setPanLimited(){ // goal is, to keep at least one corner of the canvas witin a part of the screen area at all times accounting for rotation and zoom double safeAreaHeight = App::Height / 2.0f; double safeAreaWidth = App::Width / 2.0f;
// seperate axis theorem // half-dimensions float Shx = safeAreaWidth / 2.0f; float Shy = safeAreaHeight / 2.0f; float Chx = (LayerManagementContext::FinalWidth * Zoom) / 2.0f; float Chy = (LayerManagementContext::FinalHeight * Zoom) / 2.0f;
// rotation components float cosR = cosf(Rotation); float sinR = sinf(Rotation); float absCos = fabsf(cosR); float absSin = fabsf(sinR);
// siffrin for (int i = 0; i < 3; i++) { // screen x float bound1 = Shx + Chx * absCos + Chy * absSin; if (PanX > bound1) PanX -= (PanX - bound1); else if (PanX < -bound1) PanX += (-bound1 - PanX);
// screen y float bound2 = Shy + Chx * absSin + Chy * absCos; if (PanY > bound2) PanY -= (PanY - bound2); else if (PanY < -bound2) PanY += (-bound2 - PanY);
// local x float p3 = PanX * cosR + PanY * sinR; float bound3 = Chx + Shx * absCos + Shy * absSin; if (p3 > bound3) { PanX -= (p3 - bound3) * cosR; PanY -= (p3 - bound3) * sinR; } else if (p3 < -bound3) { PanX += (-bound3 - p3) * cosR; PanY += (-bound3 - p3) * sinR; }
// local y float p4 = -PanX * sinR + PanY * cosR; float bound4 = Chy + Shx * absSin + Shy * absCos; if (p4 > bound4) { PanX -= (p4 - bound4) * -sinR; PanY -= (p4 - bound4) * cosR; } else if (p4 < -bound4) { PanX += (-bound4 - p4) * -sinR; PanY += (-bound4 - p4) * cosR; } }}
void CanvasAppLayer::renderToSwapchain(){ SDL_GPUColorTargetInfo swapchainTargetInfo = {}; swapchainTargetInfo.texture = GPU::GetSwapchain(); swapchainTargetInfo.load_op = SDL_GPU_LOADOP_LOAD; swapchainTargetInfo.store_op = SDL_GPU_STOREOP_STORE; swapchainTargetInfo.mip_level = 0; swapchainTargetInfo.layer_or_depth_plane = 0; swapchainTargetInfo.cycle = false;
int width, height; SDL_GetWindowSizeInPixels(GPU::GetWindow(), &width, &height); SDL_GPUViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.w = (float)width; viewport.h = (float)height;
//setVertexBuffer({ 0.0f, 0.0f, (float)width, (float)height }, { 0.0f, 0.0f, (float)renderingTarget->GetWidth(), (float)renderingTarget->GetHeight() });
setVertexBufferCanvas(); renderTransparencyField(&swapchainTargetInfo, viewport);
if (preview) { renderPreviewStage(swapchainTargetInfo, viewport); } else { //SDL_FRect viewrect = *((SDL_FRect*)&viewport); //setVertexBuffer(viewrect, { 0.0f, 0.0f, (float)renderingTarget->GetWidth(), (float)renderingTarget->GetHeight() }); setVertexBufferCanvas(); renderFinalCompositeToTarget({ &swapchainTargetInfo }, layerRenderingContext->CompositeAll(LayerManagementContext::Layers), viewport, pipelineBlend, canvasBuffer); // ask LayerRenderingContext for composite → replace renderingTarget }
}
void CanvasAppLayer::renderFinalCompositeToTarget(std::list<SDL_GPUColorTargetInfo*> targets, Texture* source, SDL_GPUViewport viewport, GraphicsPipeline* pipeline, GPUBuffer* inputBuffer, float opacity){ GPU::BeginRendering(targets, nullptr); SDL_SetGPUViewport(GPU::GetRenderPass(), &viewport); pipeline->Bind(); SDL_GPUBufferBinding vertexBufferBinding = { .buffer = inputBuffer->Get(), .offset = 0 }; SDL_BindGPUVertexBuffers(GPU::GetRenderPass(), 0, &vertexBufferBinding, 1); SDL_GPUTextureSamplerBinding textureSamplerBinding = { .texture = source->Get(), .sampler = sampler }; SDL_BindGPUFragmentSamplers(GPU::GetRenderPass(), 0, &textureSamplerBinding, 1); float pushConstants[1] = { opacity }; SDL_PushGPUFragmentUniformData(GPU::GetCommandBuffer(), 0, pushConstants, sizeof(pushConstants)); SDL_DrawGPUPrimitives(GPU::GetRenderPass(), 4, 1, 0, 0); // draw canvas GPU::EndRendering();}
void CanvasAppLayer::renderTransparencyField(SDL_GPUColorTargetInfo* target, SDL_GPUViewport viewport){ float lightCol[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; float darkCol[4] = { 0.75f, 0.75f, 0.75f, 1.0f }; float darkerCol[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; switch (LayerManagementContext::Background) { case BackgroundMode::COLOR: memcpy(lightCol, LayerManagementContext::BackgroundColor, sizeof(float) * 4); memcpy(darkCol, LayerManagementContext::BackgroundColor, sizeof(float) * 4); break; case BackgroundMode::TRANSPARENT: break; // already set case BackgroundMode::TRANSPARENT_DARK: memcpy(lightCol, darkCol, sizeof(float) * 4); memcpy(darkCol, darkerCol, sizeof(float) * 4); break; } GPU::BeginRendering({ target }, nullptr); SDL_SetGPUViewport(GPU::GetRenderPass(), &viewport); pipelineTransparencyField->Bind(); SDL_GPUBufferBinding vertexBufferBinding = { .buffer = canvasBuffer->Get(), .offset = 0 }; SDL_BindGPUVertexBuffers(GPU::GetRenderPass(), 0, &vertexBufferBinding, 1); float pushConstants[13] = { // light col lightCol[0], lightCol[1], lightCol[2], lightCol[3], // dark col darkCol[0], darkCol[1], darkCol[2], darkCol[3], // frag size width and height (float)LayerManagementContext::FinalWidth, (float)LayerManagementContext::FinalHeight, // gridsize 16.0f }; SDL_PushGPUFragmentUniformData(GPU::GetCommandBuffer(), 0, pushConstants, sizeof(pushConstants));
SDL_DrawGPUPrimitives(GPU::GetRenderPass(), 4, 1, 0, 0); // draw canvas
GPU::EndRendering();}
void CanvasAppLayer::OnActivate(){ uploadBuffers(); createPipelines();
layerRenderingContext = new LayerRenderingContext();
CreateCanvas(LayerManagementContext::FinalWidth, LayerManagementContext::FinalHeight); isActive = true;}
bool CanvasAppLayer::OnDown(std::shared_ptr<PointerStream> stream){ if (!Tool::GetActive()) { return false; } if (PanelManagementContext::GetMode() != PanelManagementMode::DEFAULT) { return false; } if (Popup::GetCount() > 0) { return false; } PointerAction initialAction = (PointerAction)stream->GetInitialInput().action; if ((InputHandler::IsKeyHeld(SDLK_LSHIFT) && InputHandler::IsKeyHeld(SDLK_SPACE)) || InputHandler::IsKeyHeld(SDLK_R) || initialAction == PointerAction::SecondaryMouseButton) { // TODO: Customizable for free unlike greedy ibispaint ver.14 initialRotation = Rotation; initialPanX = PanX; initialPanY = PanY; rotateStreams.push_back(stream); return true; } else if ((InputHandler::IsKeyHeld(SDLK_LCTRL) && InputHandler::IsKeyHeld(SDLK_SPACE)) || InputHandler::IsKeyHeld(SDLK_Z)) { initialZoom = Zoom; initialPanX = PanX; initialPanY = PanY; zoomStreams.push_back(stream); return true; } else if (InputHandler::IsKeyHeld(SDLK_SPACE) || initialAction == PointerAction::TertiaryMouseButton) { panStreams.push_back(stream); return true; } stream->ApplyCanvasTranslation(Zoom, PanX, PanY, Rotation, LayerManagementContext::FinalWidth, LayerManagementContext::FinalHeight); return Tool::GetActive()->OnDown(stream);}
bool CanvasAppLayer::OnScroll(SDL_MouseWheelEvent e){ initialPanX = PanX; initialPanY = PanY; initialZoom = Zoom; // TODO: Redundant values double largestDimension = LayerManagementContext::FinalWidth > LayerManagementContext::FinalHeight ? LayerManagementContext::FinalWidth : LayerManagementContext::FinalHeight; double largestScreenDimension = App::Height > App::Width ? App::Height : App::Width; double saneZoomOut = 1.0f / largestDimension * largestScreenDimension / 4.0f; double saneZoomIn = 32.0f; // 3200% Zoom = initialZoom * SDL_exp(e.y / 8.0f); if (Zoom < saneZoomOut) Zoom = saneZoomOut; if (Zoom > saneZoomIn) Zoom = saneZoomIn; float anchorCanvasX = InputHandler::CursorX - (App::Width / 2.0f); float anchorCanvasY = -InputHandler::CursorY + (App::Height / 2.0f); // Keep anchor float zoomRatio = Zoom / initialZoom; PanX = anchorCanvasX - zoomRatio * (anchorCanvasX - initialPanX); PanY = anchorCanvasY - zoomRatio * (anchorCanvasY - initialPanY); NotifyAppLayer::SetMessage(std::to_string((int)(Zoom * 100)) + "%"); return true;}
void CanvasAppLayer::OnRender(){ preview = nullptr; if (Tool::GetActive()) { Tool::GetActive()->OnRender(); preview = Tool::GetActive()->GetPreview();
}
if (panStreams.size() > 0) { float deltaX = 0, deltaY = 0; panStreams.front()->PullDeltaInput(&deltaX, &deltaY); PanX += deltaX; PanY += deltaY;
float cosA = cosf(-Rotation); float sinA = sinf(-Rotation);
double axisPanX = (PanX * cosA) - (PanY * sinA); double axisPanY = (PanX * sinA) + (PanY * cosA); NotifyAppLayer::SetMessage("X: " + std::to_string((int)SDL_floor(-axisPanX / Zoom)) + ", Y: " + std::to_string((int)SDL_floor(-axisPanY / Zoom))); if (panStreams.front()->GetComplete()) { panStreams.pop_front();
} } if (rotateStreams.size() > 0) { auto init = rotateStreams.front()->GetInitialInput(); auto latest = rotateStreams.front()->PullRawInputs().back(); // Rotate center float initialAngle = atan2f(init.screenY - (App::Height / 2.0f), init.screenX - (App::Width / 2.0f)); float latestAngle = atan2f(latest.screenY - (App::Height / 2.0f), latest.screenX - (App::Width / 2.0f)); float deltaAngle = latestAngle - initialAngle; Rotation = initialRotation - deltaAngle; // Adjust pan with angles (canvas rotation pivot is center) float cosA = cosf(-deltaAngle); float sinA = sinf(-deltaAngle);
PanX = (initialPanX * cosA) - (initialPanY * sinA); PanY = (initialPanX * sinA) + (initialPanY * cosA); if (Rotation > M_PI) Rotation -= M_PI * 2.0f; if (Rotation < -M_PI) Rotation += M_PI * 2.0f; NotifyAppLayer::SetMessage(std::to_string((int)(Rotation * 180 / M_PI)) + "°"); if (rotateStreams.front()->GetComplete()) { rotateStreams.pop_front(); } } // sane limits double largestDimension = LayerManagementContext::FinalWidth > LayerManagementContext::FinalHeight ? LayerManagementContext::FinalWidth : LayerManagementContext::FinalHeight; double largestScreenDimension = App::Height > App::Width ? App::Height : App::Width; double saneZoomOut = 1.0f / largestDimension * largestScreenDimension / 4.0f; double saneZoomIn = 32.0f; // 3200% if (zoomStreams.size() > 0) { auto init = zoomStreams.front()->GetInitialInput(); auto latest = zoomStreams.front()->PullRawInputs().back(); // Zoom float drag = init.screenY - latest.screenY; float t = drag / App::Height; // normalized: -1..1 range roughly Zoom = initialZoom * SDL_exp(t * 2.0f); if (Zoom < saneZoomOut) Zoom = saneZoomOut; if (Zoom > saneZoomIn) Zoom = saneZoomIn; // Adjust pan with zoom (relative to center) float anchorCanvasX = init.screenX - (App::Width / 2.0f); float anchorCanvasY = -init.screenY + (App::Height / 2.0f); // Keep anchor float zoomRatio = Zoom / initialZoom; PanX = anchorCanvasX - zoomRatio * (anchorCanvasX - initialPanX); PanY = anchorCanvasY - zoomRatio * (anchorCanvasY - initialPanY); NotifyAppLayer::SetMessage(std::to_string((int)(Zoom * 100)) + "%"); if (zoomStreams.front()->GetComplete()) { zoomStreams.pop_front(); } }
setPanLimited();
renderToSwapchain();
// TODO: implement preview cursor thing}
void CanvasAppLayer::CreateCanvas(int width, int height){ SDL_GPUTextureCreateInfo creationInfo = {}; creationInfo.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; // TODO: make this configurable creationInfo.width = width; creationInfo.height = height; creationInfo.layer_count_or_depth = 1; creationInfo.num_levels = 1; creationInfo.sample_count = SDL_GPU_SAMPLECOUNT_1; creationInfo.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; localPreview = new Texture(creationInfo); SDL_SetGPUTextureName(GPU::GetDevice(), localPreview->Get(), "Canvas Preview // Scratch Texture");}
void CanvasAppLayer::OnDeactivate() { isActive = false;}
double CanvasAppLayer::Zoom = 1.0;double CanvasAppLayer::PanX = 0.0;double CanvasAppLayer::PanY = 0.0;double CanvasAppLayer::Rotation = 0.0; // 90 degrees