good comp panel

This commit is contained in:
lachrymaLF 2024-05-25 09:06:43 -04:00
parent 6eaa92cdf8
commit 2f7378f218
16 changed files with 2387 additions and 344 deletions

3
.gitmodules vendored
View file

@ -7,3 +7,6 @@
[submodule "Keishiki/ext/freetype"]
path = Keishiki/ext/freetype
url = https://github.com/freetype/freetype.git
[submodule "Keishiki/ext/ImNodeFlow"]
path = Keishiki/ext/ImNodeFlow
url = https://github.com/Fattorino/ImNodeFlow.git

View file

@ -11,11 +11,20 @@ add_executable (Keishiki
"ext/imgui/misc/cpp/imgui_stdlib.cpp"
"Keishiki.cpp" "Graphics.cpp" "UI.cpp" "ShaderGraph.cpp")
set_property(TARGET Keishiki PROPERTY CXX_STANDARD 23)
include_directories ("include" "include/ext" "ext/imgui")
add_subdirectory("ext/freetype")
add_subdirectory("ext/bgfx")
#add_subdirectory("ext/ImNodeFlow")
add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS)
#target_link_libraries(Keishiki ImNodeFlow)
if (WIN32)
include_directories("include/windows")
target_link_libraries (Keishiki PRIVATE freetype bgfx bx ${PROJECT_SOURCE_DIR}/lib/x64/SDL2.lib ${PROJECT_SOURCE_DIR}/lib/x64/SDL2main.lib)

View file

@ -18,9 +18,11 @@ namespace {
u16 window_height = 1080;
u64 init_time, last_time, current_time, delta_t;
u32 frame;
bool running = false;
K::CompState state;
K::Graphics::ImageTexture *logo;
}
namespace K {
@ -72,10 +74,14 @@ namespace K {
return false;
}
bgfx::setDebug(BGFX_DEBUG_TEXT);
bgfx::setViewClear(K::Graphics::K_VIEW_TOP, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x000000ff, 1.0f, 0);
bgfx::setViewRect(K::Graphics::K_VIEW_TOP, 0, 0, window_width, window_height);
bgfx::setViewClear(K::Graphics::K_VIEW_BG, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x000000ff, 1.0f, 0);
bgfx::setViewRect(K::Graphics::K_VIEW_BG, 0, 0, window_width, window_height);
float view[16];
bx::mtxTranslate(view, 0.f, 0.f, 1.0f);
float proj[16];
bx::mtxOrtho(proj, 0.0f, window_width, 0.0f, window_height, 0.1f, 100.0f, 0.f, bgfx::getCaps()->homogeneousDepth);
bgfx::setViewTransform(K::Graphics::K_VIEW_BG, view, proj);
K::UI::Init(window);
@ -84,25 +90,35 @@ namespace K {
return false;
}
state.width = 800;
state.height = 600;
logo = K::Graphics::GetImageTextureFromFile("Keishiki.png");
state.width = 1280;
state.height = 550;
K::UI::SetupViewport(state);
return true;
}
void Render() {
UI::Draw(state);
K::Graphics::DrawTextureImageAlpha(K::Graphics::K_VIEW_BG, *logo, window_width - logo->w + 10, 0, .7f);
UI::Draw(frame, state);
const bgfx::Stats *stat = bgfx::getStats();
const bgfx::Caps *caps = bgfx::getCaps();
bgfx::dbgTextClear();
bgfx::dbgTextPrintf(0, window_height/16 - 4, 0xf8, " lachrymal.net :: %s Renderer :: %u FPS", caps->supported & BGFX_CAPS_RENDERER_MULTITHREADED ? "Multithreaded" : "Singlethreaded", stat->cpuTimerFreq / stat->cpuTimeFrame);
bgfx::dbgTextPrintf(0, window_height/16 - 3, 0xf8, " Max Views %u :: Max FBs %u :: Max Texture Samplers %u :: Blitting %s", caps->limits.maxViews, caps->limits.maxFrameBuffers, caps->limits.maxTextureSamplers, caps->supported & BGFX_CAPS_TEXTURE_BLIT ? "OK" : "NO");
bgfx::dbgTextPrintf(0, window_height/16 - 2, 0xf8, " Project Keishiki :: %s :: Build %s %s", BX_COMPILER_NAME, __DATE__, __TIME__);
bgfx::dbgTextPrintf(0, window_height/16 - 1, 0xf8, " SDL %i.%i.%i :: bgfx 1.%i :: Dear ImGui %s :: %s", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL, BGFX_API_VERSION, ImGui::GetVersion(), bgfx::getRendererName(bgfx::getRendererType()));
bgfx::dbgTextPrintf(0, window_height/16 - 2, 0xf8,
" lachrymal.net :: %s Renderer :: Max Views %u :: Max FBs %u :: Max Texture Samplers %u :: Blitting %s :: %u FPS",
caps->supported & BGFX_CAPS_RENDERER_MULTITHREADED ? "Multithreaded" : "Singlethreaded",
caps->limits.maxViews, caps->limits.maxFrameBuffers,
caps->limits.maxTextureSamplers, caps->supported & BGFX_CAPS_TEXTURE_BLIT ? "OK" : "NO",
stat->cpuTimerFreq / stat->cpuTimeFrame);
bgfx::dbgTextPrintf(0, window_height/16 - 1, 0xf8,
" Project Keishiki :: %s :: Build %s %s :: SDL %i.%i.%i :: bgfx 1.%i :: Dear ImGui %s :: %s",
BX_COMPILER_NAME, __DATE__, __TIME__, SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL,
BGFX_API_VERSION, ImGui::GetVersion(), bgfx::getRendererName(bgfx::getRendererType()));
bgfx::frame();
frame = bgfx::frame();
}
void Run() {

View file

@ -1,5 +1,4 @@
#include "UI.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#include "ext/imgui/misc/cpp/imgui_stdlib.h"
#include <imgui.h>
@ -9,19 +8,30 @@
#include "backends/imgui_impl_sdl2.h"
#include "imgui_impl_bgfx.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
namespace {
bool draw_viewport = true;
bool draw_sequencer = true;
bool draw_nodes = true;
// windows
bool draw_viewport = true,
draw_plugboard = true,
draw_shader = true,
draw_comp = true;
const f32 row_height = 20.0f;
// Viewport
bgfx::TextureHandle composite = BGFX_INVALID_HANDLE,
composite_blit = BGFX_INVALID_HANDLE,
render = BGFX_INVALID_HANDLE;
render = BGFX_INVALID_HANDLE,
save = BGFX_INVALID_HANDLE;
bgfx::FrameBufferHandle composite_fb = BGFX_INVALID_HANDLE,
render_fb = BGFX_INVALID_HANDLE;
K::VisualTrack bg{};
f32 proj[16], transform[16], view[16];
K::Byte *save_buffer;
bool save_called = false;
u32 ready_frame;
}
@ -39,20 +49,20 @@ namespace ImGui {
namespace K::UI {
void MainMenuBar(const CompState& s) {
if (ImGui::BeginMainMenuBar()) {
//if (ImGui::BeginMenu("Edit")) {
if (ImGui::BeginMenu("Edit")) {
// if (ImGui::MenuItem("Undo", "CTRL+Z")) {}
// if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item
// ImGui::Separator();
// if (ImGui::MenuItem("Cut", "CTRL+X")) {}
// if (ImGui::MenuItem("Copy", "CTRL+C")) {}
// if (ImGui::MenuItem("Paste", "CTRL+V")) {}
// ImGui::EndMenu();
//}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View")) {
if (ImGui::MenuItem("Viewport", "", &draw_viewport)) {}
if (ImGui::MenuItem("Sequencer", "", &draw_sequencer)) {}
if (ImGui::MenuItem("Shader", "", &draw_nodes)) {}
if (ImGui::MenuItem("Plugboard", "", &draw_plugboard)) {}
if (ImGui::MenuItem("Shader", "", &draw_shader)) {}
ImGui::EndMenu();
}
}
@ -69,6 +79,9 @@ namespace K::UI {
composite_fb = bgfx::createFrameBuffer(1, &comp);
render_fb = bgfx::createFrameBuffer(1, &ren);
save = bgfx::createTexture2D(s.width, s.height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_READ_BACK | BGFX_TEXTURE_BLIT_DST);
save_buffer = static_cast<Byte *>(std::malloc(s.width * s.height * 4));
bg.pg = K::Graphics::load_shader_program("Checkerboard");
bg.add_uniform("f_hw", ShaderGraph::Type::T_XYZ);
bg.uniforms.begin()->second.second = ShaderGraph::XYZ{ static_cast<f32>(s.width), static_cast<f32>(s.height), 0.0f };
@ -89,18 +102,15 @@ namespace K::UI {
bgfx::destroy(render_fb);
bgfx::destroy(render);
bgfx::destroy(save);
std::free(save_buffer);
bgfx::destroy(bg.pg);
bgfx::destroy(bg.uniforms.begin()->second.first);
bg.uniforms.erase(bg.uniforms.begin());
for (auto& layer : s.layers) {
if (bgfx::isValid(layer.track.pg))
bgfx::destroy(layer.track.pg);
for (auto& uniform : layer.track.uniforms) {
bgfx::destroy(uniform.second.first);
}
layer.track.uniforms.clear();
}
for (auto& layer : s.layers)
layer.track.clear();
}
void Viewport(CompState& s) {
@ -109,58 +119,171 @@ namespace K::UI {
bg.get_frame(composite_fb, s.width, s.height, proj, view, transform);
bgfx::blit(Graphics::K_VIEW_COMP_COMPOSITE, composite_blit, 0, 0, composite);
for (auto it = s.layers.rbegin(); it != s.layers.rend(); it++) {
if (!it->enabled) continue;
Graphics::Composite(composite_fb, composite_blit, it->track.get_frame(render_fb, s.width, s.height, proj, view, transform),
it == s.layers.rbegin() ? Graphics::K_V_AlphaOver : it->mode, s.width, s.height, proj, transform);
for (u32 i = s.layers.size() - 1; i != u32(-1); i--) {
if (s.disabled.contains(i)) continue;
Graphics::Composite(composite_fb, composite_blit, s.layers[i].track.get_frame(render_fb, s.width, s.height, proj, view, transform),
i == s.layers.size() - 1 ? Graphics::K_V_AlphaOver : s.layers[i].mode, s.width, s.height, proj, transform);
bgfx::blit(Graphics::K_VIEW_COMP_COMPOSITE, composite_blit, 0, 0, composite);
}
idx = ImGui::toId(composite, 0, 0);
if (idx != nullptr)
ImGui::Image(idx, ImVec2{ static_cast<f32>(s.width), static_cast<f32>(s.height) });
ImGui::Text("Comp Size: %ux%u", s.width, s.height);
ImGui::Text("Composition Size: %ux%u", s.width, s.height);
auto cp = save_called;
if (cp) ImGui::BeginDisabled();
if (ImGui::Button("Save to frame.png")) {
bgfx::blit(Graphics::K_VIEW_COMP_SAVE, save, 0, 0, composite);
ready_frame = bgfx::readTexture(save, save_buffer);
save_called = true;
}
if (cp) ImGui::EndDisabled();
}
ImGui::End();
}
void Sequencer(CompState& s){
if (ImGui::Begin("Sequencer", &draw_sequencer)) {
void Composition(CompState& s) {
if (!ImGui::Begin("Composition", &draw_plugboard)) {
ImGui::End();
return;
}
const bool no_selection = s.selected.empty();
static String name{};
if (ImGui::Button("Add Layer") && !name.empty()) {
s.layers.push_back(Layer{
auto l = Layer{
VisualTrack{},
name,
true,
false,
Graphics::K_V_AlphaOver
});
};
l.track.add_uniform("aspect_ratio", ShaderGraph::expand_type(ShaderGraph::T_XY));
if (s.width > s.height)
l.track.uniforms["aspect_ratio"].second = ShaderGraph::T_Map<ShaderGraph::T_XY>::type(static_cast<f32>(s.width)/static_cast<f32>(s.height), 1.0f);
else
l.track.uniforms["aspect_ratio"].second = ShaderGraph::T_Map<ShaderGraph::T_XY>::type(1.0f, static_cast<f32>(s.height)/static_cast<f32>(s.width));
l.track.add_uniform("opacity", ShaderGraph::expand_type(ShaderGraph::T_Float));
l.track.uniforms["opacity"].second = ShaderGraph::T_Map<ShaderGraph::T_Float>::type(1.0f);
l.track.add_uniform("rot", ShaderGraph::expand_type(ShaderGraph::T_Float));
l.track.uniforms["rot"].second = ShaderGraph::T_Map<ShaderGraph::T_Float>::type(.0f);
l.track.add_uniform("scale", ShaderGraph::expand_type(ShaderGraph::T_XY));
l.track.uniforms["scale"].second = ShaderGraph::T_Map<ShaderGraph::T_XY>::type(1.0f, 1.0f);
l.track.add_uniform("translate", ShaderGraph::expand_type(ShaderGraph::T_XY));
l.track.uniforms["translate"].second = ShaderGraph::T_Map<ShaderGraph::T_XY>::type(.0f, .0f);
l.track.shader = "void main() {\n"
"\tvec2 uv = vec2(v_texcoord0.x, 1.0f - v_texcoord0.y) - .5f;\n"
"\tuv = uv - translate;\n"
"\tuv = uv * aspect_ratio;\n"
"\tuv = vec2(cos(rot)*uv.x - sin(rot)*uv.y, sin(rot)*uv.x + cos(rot)*uv.y);\n"
"\tuv = uv / scale;\n"
"\tuv = uv + .5f;\n\n"
"\tvec4 tx = texture2D(s_texColor, uv);\n"
"\tgl_FragColor = vec4(tx.rgb, tx.a * opacity);\n"
"}\n";
l.track.compile();
s.layers.push_back(std::move(l));
}
ImGui::SameLine();
ImGui::InputText("##UniformName", &name);
ImGui::InputText("##LayerName", &name);
ImGui::SameLine();
if (no_selection) ImGui::BeginDisabled();
if (ImGui::Button("Delete Layer")) {
if (s.selected.contains(s.active))
s.active = -1;
u32 deleted = 0, before_active = 0;
const u32 sz = s.layers.size();
for (u32 i = 0; i < sz; i++) {
if (s.selected.contains(i)) {
s.layers[i - deleted].track.clear();
s.layers.erase(s.layers.begin() + i - deleted);
s.disabled.erase(i);
if (static_cast<i32>(i) < s.active)
before_active++;
deleted++;
}
else if (s.disabled.contains(i)) {
s.disabled.erase(i);
s.disabled.insert(i - deleted);
}
}
s.selected.clear();
s.active -= static_cast<i32>(before_active);
}
if (no_selection) ImGui::EndDisabled();
if (ImGui::BeginTable("Layers", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
if (ImGui::BeginTable("Layers", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("Blending", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("Enable", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("##", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("Blending", ImGuiTableColumnFlags_NoSort, 100.0f);
ImGui::TableSetupColumn("##EnableCol", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupScrollFreeze(1, 1);
ImGui::TableHeadersRow();
i32 move_from = -1, move_to = -1; bool after{};
for (u32 i = 0; i < s.layers.size(); i++) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
const bool selected = s.selected.contains(i), disabled = s.disabled.contains(i);
ImGui::PushID(static_cast<i32>(i));
ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
ImGui::TableSetColumnIndex(0);
ImGui::Text("%u", i);
ImGui::TableNextColumn();
if (ImGui::Selectable((s.layers[i].name + "##" + std::to_string(i)).c_str(), &s.layers[i].selected)) {
s.active = s.layers[i].selected ? i : -1;
if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held
for (u32 j = 0; j < s.layers.size(); j++)
s.layers[j].selected = j == i && s.layers[j].selected;
ImGui::TableSetColumnIndex(1);
if (ImGui::Selectable(s.layers[i].name.c_str(), selected,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
ImVec2(0, row_height))) {
const auto& io = ImGui::GetIO();
if (io.KeyCtrl) {
if (selected)
s.selected.erase(i);
else
s.selected.insert(i);
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(100.0f);
if (ImGui::BeginCombo(("##Blending" + std::to_string(i)).c_str(), Graphics::BlendingToString[s.layers[i].mode])) {
else if (io.KeyShift) {
// TODO
}
else {
s.selected.clear();
if (!selected)
s.selected.insert(i);
}
s.active = selected ? -1 : static_cast<i32>(i);
}
auto source_flags = ImGuiDragDropFlags_SourceNoDisableHover | ImGuiDragDropFlags_SourceNoHoldToOpenOthers;
if (!no_selection) source_flags |= ImGuiDragDropFlags_SourceNoPreviewTooltip;
if ((no_selection || s.selected.contains(i)) && ImGui::BeginDragDropSource(source_flags)) {
if (s.selected.empty()) ImGui::Text("Moving From #%u %s", i, s.layers[i].name.c_str());
ImGui::SetDragDropPayload("DND_DEMO_NAME", &i, sizeof(u32));
ImGui::EndDragDropSource();
}
if ((no_selection || !s.selected.contains(i)) && ImGui::BeginDragDropTarget()) {
if (!no_selection) {
after = ImGui::GetMousePos().y - ImGui::GetWindowPos().y > ImGui::GetCursorPosY() - row_height / 2.0f;
if (after)
ImGui::SetTooltip("After #%u %s", i, s.layers[i].name.c_str());
else
ImGui::SetTooltip("Before #%u %s", i, s.layers[i].name.c_str());
}
if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("DND_DEMO_NAME")) {
move_from = *(const int*)payload->Data;
move_to = i;
}
ImGui::EndDragDropTarget();
}
ImGui::TableSetColumnIndex(2);
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::BeginCombo("##Blending", Graphics::BlendingToString[s.layers[i].mode])) {
for (i32 b = Graphics::K_V_AlphaOver; b != Graphics::K_V_Count; b++) {
const bool is_selected = (static_cast<Graphics::Blending>(b) == s.layers[i].mode);
if (ImGui::Selectable(Graphics::BlendingToString[b], is_selected))
@ -170,42 +293,74 @@ namespace K::UI {
}
ImGui::EndCombo();
}
ImGui::TableNextColumn();
ImGui::Checkbox(("##Enable" + std::to_string(i)).c_str(), &s.layers[i].enabled);
ImGui::TableSetColumnIndex(3);
bool cp = !disabled;
ImGui::Checkbox("##Enable", &cp);
if (cp == disabled) {
if (disabled)
s.disabled.erase(i);
else
s.disabled.insert(i);
}
ImGui::PopID();
}
if (move_from != -1 && move_to != -1) {
if (no_selection)
std::swap(s.layers[move_to], s.layers[move_from]);
else {
const u32 n = s.selected.size();
u32 inserted = 0;
u32 target = move_to + after, popped_before_target = 0;
for (auto it = s.selected.rbegin(); it != s.selected.rend(); it++, inserted++) {
u32 sel = *it;
if (sel > target) sel += inserted; // note that we iterate backwards, so cur index is always right if < target
Layer l = s.layers[sel];
s.layers.erase(s.layers.begin() + sel);
if (sel < move_to) popped_before_target++;
s.layers.emplace(s.layers.begin() + target - popped_before_target, l);
}
s.selected.clear();
for (u32 j = 0; j < n; j++)
s.selected.insert(target - popped_before_target + j);
}
}
ImGui::EndTable();
}
ImGui::End();
}
void Plugboard(CompState& s) {
if (ImGui::Begin("Plugboard", &draw_plugboard)) {
if (ImGui::BeginTable("Source", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit, { 50.0f, -FLT_MIN })) {
ImGui::PushID(1);
ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
ImGui::TableNextColumn();
ImGui::Text("Time");
ImGui::PopID();
ImGui::EndTable();
}
ImGui::SameLine();
if (ImGui::InvisibleButton("Graph", ImVec2{200.0f, 200.0f})) {
Log("d");
}
}
ImGui::End();
}
/*void Nodegraph(CompState& s) {
Layer* active = nullptr;
u32 active_index = 0;
while (active_index < s.layers.size()) {
if (s.layers[active_index].selected) {
active = &s.layers[active_index];
break;
}
active_index++;
} // should not need to do this every frame
if (!ImGui::Begin("Nodegraph", &draw_nodes)) {
ImGui::End();
return;
}
void Nodegraph(CompState& s) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems) {
ImGui::End();
return;
}
if (active == nullptr) {
ImGui::Text("No layer selected.");
ImGui::End();
return;
}
else {
ImGuiIO& io = ImGui::GetIO();
ImGui::Text("Inspecting layer #%u \"%s\"", active_index, active->name.c_str());
@ -323,56 +478,72 @@ namespace K::UI {
}
ImGui::End();
}*/
}
void Shader(CompState& s) {
if (ImGui::Begin("Shader", &draw_nodes)) {
if (ImGui::Begin("Shader", &draw_shader)) {
if (s.active == -1)
ImGui::Text("No active layer.");
else {
static int type_current = 0;
static String name{};
if (ImGui::Button("Add Uniform") && !name.empty()) {
if (ImGui::Button("Add Uniform") && !name.empty() && !isdigit(name[0]) && name[0] != 'f') {
s.layers[s.active].track.add_uniform(name, ShaderGraph::expand_type(static_cast<ShaderGraph::Type>(type_current)));
}
ImGui::SameLine();
ImGui::InputText("##UniformName", &name);
struct TextFilters { static int VariableName(ImGuiInputTextCallbackData *data) {
return !(data->EventChar < 256 &&
((data->EventChar >= 'a' && data->EventChar <= 'z') ||
(data->EventChar >= 'A' && data->EventChar <= 'Z')||
(data->EventChar >= '0' && data->EventChar <= '9') ||
data->EventChar == '_'));
} };
ImGui::InputText("##UniformName", &name, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::VariableName);
ImGui::SameLine();
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::Combo("Type", &type_current, ShaderGraph::Type_To_Str, ShaderGraph::T_Count);
if (ImGui::BeginTable("Uniforms", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
if (ImGui::BeginTable("Uniforms", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("Expose", ImGuiTableColumnFlags_NoSort);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoSort, ImGui::GetContentRegionAvail().x / 1.7f);
ImGui::TableSetupColumn("##del", ImGuiTableColumnFlags_NoSort);
ImGui::TableHeadersRow();
i32 i = 0;
for (auto it = s.layers[s.active].track.uniforms.begin(); it != s.layers[s.active].track.uniforms.end();) {
ImGui::TableNextRow();
ImGui::PushID(i++);
ImGui::TableNextColumn();
ImGui::Text("%s", it->first.c_str());
ImGui::TableNextColumn();
ImGui::Text("%s", ShaderGraph::Type_To_Str[it->second.second.index()]);
ImGui::TableNextColumn();
bool temp{};
ImGui::Checkbox("##Expose", &temp);
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-FLT_MIN);
std::visit([it](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, ShaderGraph::T_Map<ShaderGraph::T_Float>::type>)
ImGui::DragFloat(("##" + it->first).c_str(), &arg, 0.005f);
ImGui::DragFloat("##", &arg, 0.005f);
else if constexpr (std::is_same_v<T, ShaderGraph::T_Map<ShaderGraph::T_Int>::type>)
ImGui::InputInt(("##" + it->first).c_str(), &arg);
ImGui::DragInt("##", &arg);
else if constexpr (std::is_same_v<T, ShaderGraph::T_Map<ShaderGraph::T_RGBA>::type>)
ImGui::InputFloat4(("##" + it->first).c_str(), &arg.r);
ImGui::DragFloat4("##", &arg.r);
else if constexpr (std::is_same_v<T, ShaderGraph::T_Map<ShaderGraph::T_XY>::type>)
ImGui::InputFloat2(("##" + it->first).c_str(), &arg.x);
ImGui::DragFloat2("##", &arg.x);
else if constexpr (std::is_same_v<T, ShaderGraph::T_Map<ShaderGraph::T_XYZ>::type>)
ImGui::InputFloat3(("##" + it->first).c_str(), &arg.x);
ImGui::DragFloat3("##", &arg.x);
}, it->second.second);
ImGui::TableNextColumn();
if (ImGui::Button(("Delete Uniform##" + it->first).c_str())) {
if (ImGui::Button("X")) {
bgfx::destroy(it->second.first);
it = s.layers[s.active].track.uniforms.erase(it);
}
else it++;
ImGui::PopID();
}
ImGui::EndTable();
}
@ -385,18 +556,24 @@ namespace K::UI {
ImGui::End();
}
void Draw(CompState& s) {
void Draw(u32 frame, CompState& s) {
ImGui_ImplSDL2_NewFrame();
ImGui_Implbgfx_NewFrame();
ImGui::NewFrame();
// ImGui::ShowDemoWindow();
ImGui::ShowDemoWindow();
static ImGuiStyle& style = ImGui::GetStyle();
style.GrabRounding = style.FrameRounding = 5.0f;
MainMenuBar(s);
if (draw_viewport) Viewport(s);
if (draw_nodes) Shader(s);
if (draw_sequencer) Sequencer(s);
if (draw_shader) Shader(s);
if (draw_plugboard) Plugboard(s);
if (draw_comp) Composition(s);
if (save_called && ready_frame == frame) {
stbi_write_png("frame.png", s.width, s.height, 4, save_buffer, s.width * 4);
save_called = false;
}
ImGui::Render();
ImGui_Implbgfx_RenderDrawLists(ImGui::GetDrawData());
@ -406,17 +583,28 @@ namespace K::UI {
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// io.Fonts->AddFontFromFileTTF("SourceHanSans-Regular.ttc", 17);
ImGui_Implbgfx_Init(K::Graphics::K_VIEW_TOP);
#if BX_PLATFORM_WINDOWS
switch (bgfx::getRendererType()) {
case bgfx::RendererType::Noop:
case bgfx::RendererType::Direct3D11:
case bgfx::RendererType::Direct3D12:
ImGui_ImplSDL2_InitForD3D(window);
#elif BX_PLATFORM_OSX
break;
case bgfx::RendererType::Metal:
ImGui_ImplSDL2_InitForMetal(window);
#elif BX_PLATFORM_LINUX || BX_PLATFORM_EMSCRIPTEN
break;
case bgfx::RendererType::OpenGL:
ImGui_ImplSDL2_InitForOpenGL(window, nullptr);
break;
case bgfx::RendererType::Vulkan:
ImGui_ImplSDL2_InitForVulkan(window);
#endif
break;
default:
LogError("Unsupported Renderer");
};
}
void Shutdown(CompState& s) {

@ -0,0 +1 @@
Subproject commit 3e4ed6e5b51cc9a874480b94f112d5965b0412b0

@ -1 +1 @@
Subproject commit aa5a6098ee24ca30b3e0a180282619777e95fc67
Subproject commit b39fc84f8919b87c9d79f6ffaf43efb61d2beb3d

View file

@ -7,7 +7,9 @@
namespace K::Graphics {
enum VIEW_ID {
K_VIEW_BG,
K_VIEW_COMP_COMPOSITE,
K_VIEW_COMP_SAVE,
K_VIEW_DRAW,
K_VIEW_TOP,
};
@ -22,7 +24,7 @@ namespace K::Graphics {
u8 *buffer;
bgfx::TextureHandle tx;
};
ImageTexture *GetImageTextureFromFile(const std::string& file);
ImageTexture *GetImageTextureFromFile(const std::string& file); // Caller not responsible for freeing
void DrawTextureWithTransform(u32 view_id, const bgfx::TextureHandle& tex, f32 mtx[16], u64 state, const bgfx::ProgramHandle& pg);
void DrawTexture(u32 view_id, const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h, u64 state, const bgfx::ProgramHandle& pg);

View file

@ -1,6 +1,8 @@
#pragma once
#include "Common.h"
#include "VisualTrack.h"
#include <set>
#include <unordered_set>
namespace K {
bool Init();
@ -12,7 +14,6 @@ namespace K {
struct Layer {
VisualTrack track;
String name;
bool enabled, selected;
Graphics::Blending mode;
};
@ -22,6 +23,8 @@ namespace K {
u32 fps;
u32 width, height;
Vector<Layer> layers;
i32 active = -1;
i32 active = -1; // index for layers
std::set<u32> selected; // indices for layers
std::unordered_set<u32> disabled; // indices for layers
};
}

View file

@ -2,7 +2,6 @@
#include "Common.h"
#include "Graphics.h"
#include <variant>
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui.h"
#include "Graphics.h"

View file

@ -6,7 +6,7 @@
namespace K::UI {
void Init(SDL_Window *window);
void Draw(CompState& s);
void Draw(u32 frame, CompState& s);
void Shutdown(CompState& s);
void SetupViewport(CompState& s);
}

View file

@ -21,11 +21,7 @@ namespace K {
struct VisualTrack {
ShaderGraph::ShaderGraph tree;
bgfx::ProgramHandle pg = BGFX_INVALID_HANDLE;
String shader = "void main() {\n"
"\tvec2 uv = vec2(v_texcoord0.x, 1.0f - v_texcoord0.y);\n"
"\tvec4 tx = texture2D(s_texColor, uv);\n"
"\tgl_FragColor = vec4(tx.rgb, tx.a);\n"
"}\n";
String shader;
Dict<String, std::pair<bgfx::UniformHandle, ShaderGraph::T_Map<ShaderGraph::T_Count>::type>> uniforms;
// Vector<String> samplers;
bgfx::TextureHandle get_frame(bgfx::FrameBufferHandle fb, u32 w, u32 h, f32 proj[16], f32 view[16], f32 transform[16]) const {
@ -36,7 +32,7 @@ namespace K {
bgfx::setViewTransform(K::Graphics::K_VIEW_COMP_COMPOSITE, view, proj);
bgfx::setViewRect(K::Graphics::K_VIEW_COMP_COMPOSITE, 0, 0, w, h);
// TODO Find a better way to pack...
for (auto& [_name, u] : uniforms) {
f32 pack[4]{};
std::visit([&pack](auto&& arg) {
@ -69,7 +65,7 @@ namespace K {
}
void add_uniform(const String& s, ShaderGraph::T_Map<ShaderGraph::T_Count>::type&& val) {
if (!uniforms.contains(s))
uniforms.emplace(s, std::make_pair(bgfx::createUniform(("__" + s).c_str(), bgfx::UniformType::Vec4), std::move(val)));
uniforms.emplace(s, std::make_pair(bgfx::createUniform(("__" + s).c_str(), bgfx::UniformType::Vec4), val));
}
void compile() {
std::ofstream f("temp.frag");
@ -143,6 +139,15 @@ namespace K {
else
Log("User shader compilation failed");
}
void clear() {
if (bgfx::isValid(pg))
bgfx::destroy(pg);
for (auto& uniform : uniforms) {
bgfx::destroy(uniform.second.first);
}
uniforms.clear();
}
};
void Composite();

View file

@ -1,4 +1,4 @@
/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb
/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb
no warranty implied; use at your own risk
Do this:
@ -48,6 +48,8 @@ LICENSE
RECENT REVISION HISTORY:
2.29 (2023-05-xx) optimizations
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
2.26 (2020-07-13) many minor fixes
2.25 (2020-02-02) fix warnings
@ -108,7 +110,7 @@ RECENT REVISION HISTORY:
Cass Everitt Ryamond Barbiero github:grim210
Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw
Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus
Josh Tobin Matthew Gregan github:poppolopoppo
Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo
Julian Raschke Gregory Mullen Christian Floisand github:darealshinji
Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007
Brad Weinberger Matvey Cherevko github:mosra
@ -140,7 +142,7 @@ RECENT REVISION HISTORY:
// // ... x = width, y = height, n = # 8-bit components per pixel ...
// // ... replace '0' with '1'..'4' to force that many components per pixel
// // ... but 'n' will always be the number that it would have been if you said 0
// stbi_image_free(data)
// stbi_image_free(data);
//
// Standard parameters:
// int *x -- outputs image width in pixels
@ -635,7 +637,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch
#endif
#endif
#ifdef _MSC_VER
#if defined(_MSC_VER) || defined(__SYMBIAN32__)
typedef unsigned short stbi__uint16;
typedef signed short stbi__int16;
typedef unsigned int stbi__uint32;
@ -1063,6 +1065,23 @@ static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)
}
#endif
// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow.
static int stbi__addints_valid(int a, int b)
{
if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow
if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0.
return a <= INT_MAX - b;
}
// returns 1 if the product of two ints fits in a signed short, 0 on overflow.
static int stbi__mul2shorts_valid(int a, int b)
{
if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow
if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid
if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN
return a >= SHRT_MIN / b;
}
// stbi__err - error
// stbi__errpf - error returning pointer to float
// stbi__errpuc - error returning pointer to unsigned char
@ -1985,9 +2004,12 @@ static int stbi__build_huffman(stbi__huffman *h, int *count)
int i,j,k=0;
unsigned int code;
// build size list for each symbol (from JPEG spec)
for (i=0; i < 16; ++i)
for (j=0; j < count[i]; ++j)
for (i=0; i < 16; ++i) {
for (j=0; j < count[i]; ++j) {
h->size[k++] = (stbi_uc) (i+1);
if(k >= 257) return stbi__err("bad size list","Corrupt JPEG");
}
}
h->size[k] = 0;
// compute actual symbols (from jpeg spec)
@ -2112,6 +2134,8 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)
// convert the huffman code to the symbol id
c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
if(c < 0 || c >= 256) // symbol id out of bounds!
return -1;
STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);
// convert the id to a symbol
@ -2130,6 +2154,7 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
unsigned int k;
int sgn;
if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative)
k = stbi_lrot(j->code_buffer, n);
@ -2144,6 +2169,7 @@ stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)
{
unsigned int k;
if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
k = stbi_lrot(j->code_buffer, n);
j->code_buffer = k & ~stbi__bmask[n];
k &= stbi__bmask[n];
@ -2155,6 +2181,7 @@ stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)
{
unsigned int k;
if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);
if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing
k = j->code_buffer;
j->code_buffer <<= 1;
--j->code_bits;
@ -2192,8 +2219,10 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman
memset(data,0,64*sizeof(data[0]));
diff = t ? stbi__extend_receive(j, t) : 0;
if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG");
dc = j->img_comp[b].dc_pred + diff;
j->img_comp[b].dc_pred = dc;
if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
data[0] = (short) (dc * dequant[0]);
// decode AC components, see JPEG spec
@ -2207,6 +2236,7 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman
if (r) { // fast-AC path
k += (r >> 4) & 15; // run
s = r & 15; // combined length
if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
j->code_buffer <<= s;
j->code_bits -= s;
// decode into unzigzag'd location
@ -2246,8 +2276,10 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__
if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
diff = t ? stbi__extend_receive(j, t) : 0;
if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG");
dc = j->img_comp[b].dc_pred + diff;
j->img_comp[b].dc_pred = dc;
if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
data[0] = (short) (dc * (1 << j->succ_low));
} else {
// refinement scan for DC coefficient
@ -2282,6 +2314,7 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__
if (r) { // fast-AC path
k += (r >> 4) & 15; // run
s = r & 15; // combined length
if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
j->code_buffer <<= s;
j->code_bits -= s;
zig = stbi__jpeg_dezigzag[k++];
@ -3102,6 +3135,7 @@ static int stbi__process_marker(stbi__jpeg *z, int m)
sizes[i] = stbi__get8(z->s);
n += sizes[i];
}
if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values!
L -= 17;
if (tc == 0) {
if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;
@ -3351,6 +3385,28 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
return 1;
}
static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
{
// some JPEGs have junk at end, skip over it but if we find what looks
// like a valid marker, resume there
while (!stbi__at_eof(j->s)) {
stbi_uc x = stbi__get8(j->s);
while (x == 0xff) { // might be a marker
if (stbi__at_eof(j->s)) return STBI__MARKER_none;
x = stbi__get8(j->s);
if (x != 0x00 && x != 0xff) {
// not a stuffed zero or lead-in to another marker, looks
// like an actual marker, return it
return x;
}
// stuffed zero has x=0 now which ends the loop, meaning we go
// back to regular scan loop.
// repeated 0xff keeps trying to read the next byte of the marker.
}
}
return STBI__MARKER_none;
}
// decode image to YCbCr format
static int stbi__decode_jpeg_image(stbi__jpeg *j)
{
@ -3367,25 +3423,22 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j)
if (!stbi__process_scan_header(j)) return 0;
if (!stbi__parse_entropy_coded_data(j)) return 0;
if (j->marker == STBI__MARKER_none ) {
// handle 0s at the end of image data from IP Kamera 9060
while (!stbi__at_eof(j->s)) {
int x = stbi__get8(j->s);
if (x == 255) {
j->marker = stbi__get8(j->s);
break;
}
}
j->marker = stbi__skip_jpeg_junk_at_end(j);
// if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
}
m = stbi__get_marker(j);
if (STBI__RESTART(m))
m = stbi__get_marker(j);
} else if (stbi__DNL(m)) {
int Ld = stbi__get16be(j->s);
stbi__uint32 NL = stbi__get16be(j->s);
if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG");
if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG");
} else {
if (!stbi__process_marker(j, m)) return 0;
}
m = stbi__get_marker(j);
} else {
if (!stbi__process_marker(j, m)) return 1;
m = stbi__get_marker(j);
}
}
if (j->progressive)
stbi__jpeg_finish(j);
@ -3976,6 +4029,7 @@ static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int re
unsigned char* result;
stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
if (!j) return stbi__errpuc("outofmem", "Out of memory");
memset(j, 0, sizeof(stbi__jpeg));
STBI_NOTUSED(ri);
j->s = s;
stbi__setup_jpeg(j);
@ -3989,6 +4043,7 @@ static int stbi__jpeg_test(stbi__context *s)
int r;
stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));
if (!j) return stbi__err("outofmem", "Out of memory");
memset(j, 0, sizeof(stbi__jpeg));
j->s = s;
stbi__setup_jpeg(j);
r = stbi__decode_jpeg_header(j, STBI__SCAN_type);
@ -4014,6 +4069,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
int result;
stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
if (!j) return stbi__err("outofmem", "Out of memory");
memset(j, 0, sizeof(stbi__jpeg));
j->s = s;
result = stbi__jpeg_info_raw(j, x, y, comp);
STBI_FREE(j);
@ -4121,6 +4177,7 @@ typedef struct
{
stbi_uc *zbuffer, *zbuffer_end;
int num_bits;
int hit_zeof_once;
stbi__uint32 code_buffer;
char *zout;
@ -4187,10 +4244,21 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
int b,s;
if (a->num_bits < 16) {
if (stbi__zeof(a)) {
return -1; /* report error for unexpected end of data. */
if (!a->hit_zeof_once) {
// This is the first time we hit eof, insert 16 extra padding btis
// to allow us to keep going; if we actually consume any of them
// though, that is invalid data. This is caught later.
a->hit_zeof_once = 1;
a->num_bits += 16; // add 16 implicit zero bits
} else {
// We already inserted our extra 16 padding bits and are again
// out, this stream is actually prematurely terminated.
return -1;
}
} else {
stbi__fill_bits(a);
}
}
b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
if (b) {
s = b >> 9;
@ -4254,17 +4322,25 @@ static int stbi__parse_huffman_block(stbi__zbuf *a)
int len,dist;
if (z == 256) {
a->zout = zout;
if (a->hit_zeof_once && a->num_bits < 16) {
// The first time we hit zeof, we inserted 16 extra zero bits into our bit
// buffer so the decoder can just do its speculative decoding. But if we
// actually consumed any of those bits (which is the case when num_bits < 16),
// the stream actually read past the end so it is malformed.
return stbi__err("unexpected end","Corrupt PNG");
}
return 1;
}
if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
z -= 257;
len = stbi__zlength_base[z];
if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);
z = stbi__zhuffman_decode(a, &a->z_distance);
if (z < 0) return stbi__err("bad huffman code","Corrupt PNG");
if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data
dist = stbi__zdist_base[z];
if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
if (zout + len > a->zout_end) {
if (len > a->zout_end - zout) {
if (!stbi__zexpand(a, zout, len)) return 0;
zout = a->zout;
}
@ -4408,6 +4484,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
if (!stbi__parse_zlib_header(a)) return 0;
a->num_bits = 0;
a->code_buffer = 0;
a->hit_zeof_once = 0;
do {
final = stbi__zreceive(a,1);
type = stbi__zreceive(a,2);
@ -4563,9 +4640,8 @@ enum {
STBI__F_up=2,
STBI__F_avg=3,
STBI__F_paeth=4,
// synthetic filters used for first scanline to avoid needing a dummy row of 0s
STBI__F_avg_first,
STBI__F_paeth_first
// synthetic filter used for first scanline to avoid needing a dummy row of 0s
STBI__F_avg_first
};
static stbi_uc first_row_filter[5] =
@ -4574,22 +4650,47 @@ static stbi_uc first_row_filter[5] =
STBI__F_sub,
STBI__F_none,
STBI__F_avg_first,
STBI__F_paeth_first
STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub
};
static int stbi__paeth(int a, int b, int c)
{
int p = a + b - c;
int pa = abs(p-a);
int pb = abs(p-b);
int pc = abs(p-c);
if (pa <= pb && pa <= pc) return a;
if (pb <= pc) return b;
return c;
// This formulation looks very different from the reference in the PNG spec, but is
// actually equivalent and has favorable data dependencies and admits straightforward
// generation of branch-free code, which helps performance significantly.
int thresh = c*3 - (a + b);
int lo = a < b ? a : b;
int hi = a < b ? b : a;
int t0 = (hi <= thresh) ? lo : c;
int t1 = (thresh <= lo) ? hi : t0;
return t1;
}
static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
// adds an extra all-255 alpha channel
// dest == src is legal
// img_n must be 1 or 3
static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n)
{
int i;
// must process data backwards since we allow dest==src
if (img_n == 1) {
for (i=x-1; i >= 0; --i) {
dest[i*2+1] = 255;
dest[i*2+0] = src[i];
}
} else {
STBI_ASSERT(img_n == 3);
for (i=x-1; i >= 0; --i) {
dest[i*4+3] = 255;
dest[i*4+2] = src[i*3+2];
dest[i*4+1] = src[i*3+1];
dest[i*4+0] = src[i*3+0];
}
}
}
// create the png data from post-deflated data
static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
{
@ -4597,6 +4698,8 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
stbi__context *s = a->s;
stbi__uint32 i,j,stride = x*out_n*bytes;
stbi__uint32 img_len, img_width_bytes;
stbi_uc *filter_buf;
int all_ok = 1;
int k;
int img_n = s->img_n; // copy it into a local for later
@ -4608,8 +4711,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
if (!a->out) return stbi__err("outofmem", "Out of memory");
// note: error exits here don't need to clean up a->out individually,
// stbi__do_png always does on error.
if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
img_width_bytes = (((img_n * x * depth) + 7) >> 3);
if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG");
img_len = (img_width_bytes + 1) * y;
// we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
@ -4617,188 +4723,136 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
// so just check for raw_len < img_len always.
if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
for (j=0; j < y; ++j) {
stbi_uc *cur = a->out + stride*j;
stbi_uc *prior;
int filter = *raw++;
if (filter > 4)
return stbi__err("invalid filter","Corrupt PNG");
// Allocate two scan lines worth of filter workspace buffer.
filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0);
if (!filter_buf) return stbi__err("outofmem", "Out of memory");
// Filtering for low-bit-depth images
if (depth < 8) {
if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG");
cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
filter_bytes = 1;
width = img_width_bytes;
}
prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
for (j=0; j < y; ++j) {
// cur/prior filter buffers alternate
stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes;
stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes;
stbi_uc *dest = a->out + stride*j;
int nk = width * filter_bytes;
int filter = *raw++;
// check filter type
if (filter > 4) {
all_ok = stbi__err("invalid filter","Corrupt PNG");
break;
}
// if first row, use special filter that doesn't sample previous row
if (j == 0) filter = first_row_filter[filter];
// handle first byte explicitly
for (k=0; k < filter_bytes; ++k) {
// perform actual filtering
switch (filter) {
case STBI__F_none : cur[k] = raw[k]; break;
case STBI__F_sub : cur[k] = raw[k]; break;
case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
case STBI__F_avg_first : cur[k] = raw[k]; break;
case STBI__F_paeth_first: cur[k] = raw[k]; break;
}
}
if (depth == 8) {
if (img_n != out_n)
cur[img_n] = 255; // first pixel
raw += img_n;
cur += out_n;
prior += out_n;
} else if (depth == 16) {
if (img_n != out_n) {
cur[filter_bytes] = 255; // first pixel top byte
cur[filter_bytes+1] = 255; // first pixel bottom byte
}
raw += filter_bytes;
cur += output_bytes;
prior += output_bytes;
} else {
raw += 1;
cur += 1;
prior += 1;
}
// this is a little gross, so that we don't switch per-pixel or per-component
if (depth < 8 || img_n == out_n) {
int nk = (width - 1)*filter_bytes;
#define STBI__CASE(f) \
case f: \
case STBI__F_none:
memcpy(cur, raw, nk);
break;
case STBI__F_sub:
memcpy(cur, raw, filter_bytes);
for (k = filter_bytes; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]);
break;
case STBI__F_up:
for (k = 0; k < nk; ++k)
switch (filter) {
// "none" filter turns into a memcpy here; make that explicit.
case STBI__F_none: memcpy(cur, raw, nk); break;
STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
cur[k] = STBI__BYTECAST(raw[k] + prior[k]);
break;
case STBI__F_avg:
for (k = 0; k < filter_bytes; ++k)
cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1));
for (k = filter_bytes; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1));
break;
case STBI__F_paeth:
for (k = 0; k < filter_bytes; ++k)
cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0)
for (k = filter_bytes; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes]));
break;
case STBI__F_avg_first:
memcpy(cur, raw, filter_bytes);
for (k = filter_bytes; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1));
break;
}
#undef STBI__CASE
raw += nk;
// expand decoded bits in cur to dest, also adding an extra alpha channel if desired
if (depth < 8) {
stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
stbi_uc *in = cur;
stbi_uc *out = dest;
stbi_uc inb = 0;
stbi__uint32 nsmp = x*img_n;
// expand bits to bytes first
if (depth == 4) {
for (i=0; i < nsmp; ++i) {
if ((i & 1) == 0) inb = *in++;
*out++ = scale * (inb >> 4);
inb <<= 4;
}
} else if (depth == 2) {
for (i=0; i < nsmp; ++i) {
if ((i & 3) == 0) inb = *in++;
*out++ = scale * (inb >> 6);
inb <<= 2;
}
} else {
STBI_ASSERT(depth == 1);
for (i=0; i < nsmp; ++i) {
if ((i & 7) == 0) inb = *in++;
*out++ = scale * (inb >> 7);
inb <<= 1;
}
}
// insert alpha=255 values if desired
if (img_n != out_n)
stbi__create_png_alpha_expand8(dest, dest, x, img_n);
} else if (depth == 8) {
if (img_n == out_n)
memcpy(dest, cur, x*img_n);
else
stbi__create_png_alpha_expand8(dest, cur, x, img_n);
} else if (depth == 16) {
// convert the image data from big-endian to platform-native
stbi__uint16 *dest16 = (stbi__uint16*)dest;
stbi__uint32 nsmp = x*img_n;
if (img_n == out_n) {
for (i = 0; i < nsmp; ++i, ++dest16, cur += 2)
*dest16 = (cur[0] << 8) | cur[1];
} else {
STBI_ASSERT(img_n+1 == out_n);
#define STBI__CASE(f) \
case f: \
for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
for (k=0; k < filter_bytes; ++k)
switch (filter) {
STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break;
STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
}
#undef STBI__CASE
// the loop above sets the high byte of the pixels' alpha, but for
// 16 bit png files we also need the low byte set. we'll do that here.
if (depth == 16) {
cur = a->out + stride*j; // start at the beginning of the row again
for (i=0; i < x; ++i,cur+=output_bytes) {
cur[filter_bytes+1] = 255;
}
}
}
}
// we make a separate pass to expand bits to pixels; for performance,
// this could run two scanlines behind the above code, so it won't
// intefere with filtering but will still be in the cache.
if (depth < 8) {
for (j=0; j < y; ++j) {
stbi_uc *cur = a->out + stride*j;
stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
// unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
// png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
// note that the final byte might overshoot and write more data than desired.
// we can allocate enough data that this never writes out of memory, but it
// could also overwrite the next scanline. can it overwrite non-empty data
// on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
// so we need to explicitly clamp the final ones
if (depth == 4) {
for (k=x*img_n; k >= 2; k-=2, ++in) {
*cur++ = scale * ((*in >> 4) );
*cur++ = scale * ((*in ) & 0x0f);
}
if (k > 0) *cur++ = scale * ((*in >> 4) );
} else if (depth == 2) {
for (k=x*img_n; k >= 4; k-=4, ++in) {
*cur++ = scale * ((*in >> 6) );
*cur++ = scale * ((*in >> 4) & 0x03);
*cur++ = scale * ((*in >> 2) & 0x03);
*cur++ = scale * ((*in ) & 0x03);
}
if (k > 0) *cur++ = scale * ((*in >> 6) );
if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
} else if (depth == 1) {
for (k=x*img_n; k >= 8; k-=8, ++in) {
*cur++ = scale * ((*in >> 7) );
*cur++ = scale * ((*in >> 6) & 0x01);
*cur++ = scale * ((*in >> 5) & 0x01);
*cur++ = scale * ((*in >> 4) & 0x01);
*cur++ = scale * ((*in >> 3) & 0x01);
*cur++ = scale * ((*in >> 2) & 0x01);
*cur++ = scale * ((*in >> 1) & 0x01);
*cur++ = scale * ((*in ) & 0x01);
}
if (k > 0) *cur++ = scale * ((*in >> 7) );
if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
}
if (img_n != out_n) {
int q;
// insert alpha = 255
cur = a->out + stride*j;
if (img_n == 1) {
for (q=x-1; q >= 0; --q) {
cur[q*2+1] = 255;
cur[q*2+0] = cur[q];
for (i = 0; i < x; ++i, dest16 += 2, cur += 2) {
dest16[0] = (cur[0] << 8) | cur[1];
dest16[1] = 0xffff;
}
} else {
STBI_ASSERT(img_n == 3);
for (q=x-1; q >= 0; --q) {
cur[q*4+3] = 255;
cur[q*4+2] = cur[q*3+2];
cur[q*4+1] = cur[q*3+1];
cur[q*4+0] = cur[q*3+0];
for (i = 0; i < x; ++i, dest16 += 4, cur += 6) {
dest16[0] = (cur[0] << 8) | cur[1];
dest16[1] = (cur[2] << 8) | cur[3];
dest16[2] = (cur[4] << 8) | cur[5];
dest16[3] = 0xffff;
}
}
}
}
}
} else if (depth == 16) {
// force the image data from big-endian to platform-native.
// this is done in a separate pass due to the decoding relying
// on the data being untouched, but could probably be done
// per-line during decode if care is taken.
stbi_uc *cur = a->out;
stbi__uint16 *cur16 = (stbi__uint16*)cur;
for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
*cur16 = (cur[0] << 8) | cur[1];
}
}
STBI_FREE(filter_buf);
if (!all_ok) return 0;
return 1;
}
@ -4955,7 +5009,7 @@ STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set;
static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set;
STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)
STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)
{
stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply;
stbi__unpremultiply_on_load_set = 1;
@ -5064,14 +5118,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
if (!pal_img_n) {
s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
if (scan == STBI__SCAN_header) return 1;
} else {
// if paletted, then pal_n is our final components, and
// img_n is # components to decompress/filter.
s->img_n = 1;
if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG");
// if SCAN_header, have to scan to see if we have a tRNS
}
// even with SCAN_header, have to scan to see if we have a tRNS
break;
}
@ -5103,6 +5156,8 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG");
if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG");
has_trans = 1;
// non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.
if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }
if (z->depth == 16) {
for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
} else {
@ -5115,7 +5170,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
case STBI__PNG_TYPE('I','D','A','T'): {
if (first) return stbi__err("first not IHDR", "Corrupt PNG");
if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG");
if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; }
if (scan == STBI__SCAN_header) {
// header scan definitely stops at first IDAT
if (pal_img_n)
s->img_n = pal_img_n;
return 1;
}
if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes");
if ((int)(ioff + c.length) < (int)ioff) return 0;
if (ioff + c.length > idata_limit) {
stbi__uint32 idata_limit_old = idata_limit;
@ -5498,8 +5559,22 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
psize = (info.offset - info.extra_read - info.hsz) >> 2;
}
if (psize == 0) {
if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) {
// accept some number of extra bytes after the header, but if the offset points either to before
// the header ends or implies a large amount of extra data, reject the file as malformed
int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original);
int header_limit = 1024; // max we actually read is below 256 bytes currently.
int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size.
if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) {
return stbi__errpuc("bad header", "Corrupt BMP");
}
// we established that bytes_read_so_far is positive and sensible.
// the first half of this test rejects offsets that are either too small positives, or
// negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn
// ensures the number computed in the second half of the test can't overflow.
if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) {
return stbi__errpuc("bad offset", "Corrupt BMP");
} else {
stbi__skip(s, info.offset - bytes_read_so_far);
}
}
@ -7187,12 +7262,12 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re
// Run
value = stbi__get8(s);
count -= 128;
if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
for (z = 0; z < count; ++z)
scanline[i++ * 4 + k] = value;
} else {
// Dump
if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
for (z = 0; z < count; ++z)
scanline[i++ * 4 + k] = stbi__get8(s);
}
@ -7446,10 +7521,17 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req
out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0);
if (!out) return stbi__errpuc("outofmem", "Out of memory");
stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8));
if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) {
STBI_FREE(out);
return stbi__errpuc("bad PNM", "PNM file truncated");
}
if (req_comp && req_comp != s->img_n) {
if (ri->bits_per_channel == 16) {
out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y);
} else {
out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
}
if (out == NULL) return out; // stbi__convert_format frees input on failure
}
return out;
@ -7486,6 +7568,8 @@ static int stbi__pnm_getinteger(stbi__context *s, char *c)
while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {
value = value*10 + (*c - '0');
*c = (char) stbi__get8(s);
if((value > 214748364) || (value == 214748364 && *c > '7'))
return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int");
}
return value;
@ -7516,9 +7600,13 @@ static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
stbi__pnm_skip_whitespace(s, &c);
*x = stbi__pnm_getinteger(s, &c); // read width
if(*x == 0)
return stbi__err("invalid width", "PPM image header had zero or overflowing width");
stbi__pnm_skip_whitespace(s, &c);
*y = stbi__pnm_getinteger(s, &c); // read height
if (*y == 0)
return stbi__err("invalid width", "PPM image header had zero or overflowing width");
stbi__pnm_skip_whitespace(s, &c);
maxv = stbi__pnm_getinteger(s, &c); // read max value

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -3,9 +3,15 @@
# Keishiki: Real-Time Compositor-Sequencer
## Libraries
- [SDL2](https://www.libsdl.org/)
- [FreeType](https://freetype.org/)
- [bgfx](https://github.com/bkaradzic/bgfx)
- [Dear ImGui](https://github.com/ocornut/imgui)
- [FreeType](https://freetype.org/)
- [imgui_impl_bgfx](https://gist.github.com/pr0g/aff79b71bf9804ddb03f39ca7c0c3bbb)
- [SDL2](https://www.libsdl.org/)
- [SRELL](https://www.akenotsuki.com/misc/srell/en/)
## Inspired by
- [Automaton](https://github.com/0b5vr/automaton)
- [Blender](https://www.blender.org/)
- [Adobe After Effects](https://www.adobe.com/products/aftereffects)
- [Houdini](https://www.sidefx.com/products/houdini/)

View file

@ -1,5 +1,5 @@
## Compositor
- Manage Samplers
- Manage Samplers -- (Resources in general)
- Data model for comps
- Dump and read back state, (de)serialization!!!
- std::unordered_map -> std::flat_map pending compiler support
@ -13,6 +13,5 @@
- Wait for SDL3!
## IO
- PNG Export (perhaps go stb -> bimg)
- Video import (research opencv libavcodec etc)
- Dialogs pending SDL3
- File dialogues pending SDL3