skia2/tools/viewer/ImGuiLayer.cpp
Brian Osman a5ab35cc0e Fix vertex indexing error when using ImGui skiaWidgets
skiaWidget adds fake "images" to the ImGui draw list. Those have
vertices allocated for them (even though we don't consume them). We
still need to skip over those if any ImGui widget rendering happens
after the callback, though.

Bug: skia:
Change-Id: Ia0243e600bd32d90ba97ac9fbe58b715f70aa83a
Reviewed-on: https://skia-review.googlesource.com/c/190664
Commit-Queue: Brian Osman <brianosman@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
Auto-Submit: Brian Osman <brianosman@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
2019-02-08 15:43:26 +00:00

200 lines
7.4 KiB
C++

/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "ImGuiLayer.h"
#include "SkCanvas.h"
#include "SkImage.h"
#include "SkPixmap.h"
#include "SkSwizzle.h"
#include "SkTime.h"
#include "SkVertices.h"
#include "imgui.h"
#include <stdlib.h>
#include <map>
using namespace sk_app;
ImGuiLayer::ImGuiLayer() {
// ImGui initialization:
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
// Keymap...
io.KeyMap[ImGuiKey_Tab] = (int)Window::Key::kTab;
io.KeyMap[ImGuiKey_LeftArrow] = (int)Window::Key::kLeft;
io.KeyMap[ImGuiKey_RightArrow] = (int)Window::Key::kRight;
io.KeyMap[ImGuiKey_UpArrow] = (int)Window::Key::kUp;
io.KeyMap[ImGuiKey_DownArrow] = (int)Window::Key::kDown;
io.KeyMap[ImGuiKey_PageUp] = (int)Window::Key::kPageUp;
io.KeyMap[ImGuiKey_PageDown] = (int)Window::Key::kPageDown;
io.KeyMap[ImGuiKey_Home] = (int)Window::Key::kHome;
io.KeyMap[ImGuiKey_End] = (int)Window::Key::kEnd;
io.KeyMap[ImGuiKey_Delete] = (int)Window::Key::kDelete;
io.KeyMap[ImGuiKey_Backspace] = (int)Window::Key::kBack;
io.KeyMap[ImGuiKey_Enter] = (int)Window::Key::kOK;
io.KeyMap[ImGuiKey_Escape] = (int)Window::Key::kEscape;
io.KeyMap[ImGuiKey_A] = (int)Window::Key::kA;
io.KeyMap[ImGuiKey_C] = (int)Window::Key::kC;
io.KeyMap[ImGuiKey_V] = (int)Window::Key::kV;
io.KeyMap[ImGuiKey_X] = (int)Window::Key::kX;
io.KeyMap[ImGuiKey_Y] = (int)Window::Key::kY;
io.KeyMap[ImGuiKey_Z] = (int)Window::Key::kZ;
int w, h;
unsigned char* pixels;
io.Fonts->GetTexDataAsAlpha8(&pixels, &w, &h);
SkImageInfo info = SkImageInfo::MakeA8(w, h);
SkPixmap pmap(info, pixels, info.minRowBytes());
SkMatrix localMatrix = SkMatrix::MakeScale(1.0f / w, 1.0f / h);
auto fontImage = SkImage::MakeFromRaster(pmap, nullptr, nullptr);
auto fontShader = fontImage->makeShader(&localMatrix);
fFontPaint.setShader(fontShader);
fFontPaint.setColor(SK_ColorWHITE);
fFontPaint.setFilterQuality(kLow_SkFilterQuality);
io.Fonts->TexID = &fFontPaint;
}
ImGuiLayer::~ImGuiLayer() {
ImGui::DestroyContext();
}
void ImGuiLayer::onAttach(Window* window) {
fWindow = window;
}
bool ImGuiLayer::onMouse(int x, int y, Window::InputState state, uint32_t modifiers) {
ImGuiIO& io = ImGui::GetIO();
io.MousePos.x = static_cast<float>(x);
io.MousePos.y = static_cast<float>(y);
if (Window::kDown_InputState == state) {
io.MouseDown[0] = true;
} else if (Window::kUp_InputState == state) {
io.MouseDown[0] = false;
}
return io.WantCaptureMouse;
}
bool ImGuiLayer::onMouseWheel(float delta, uint32_t modifiers) {
ImGuiIO& io = ImGui::GetIO();
io.MouseWheel += delta;
return true;
}
void ImGuiLayer::skiaWidget(const ImVec2& size, SkiaWidgetFunc func) {
intptr_t funcIndex = fSkiaWidgetFuncs.count();
fSkiaWidgetFuncs.push_back(func);
ImGui::Image((ImTextureID)funcIndex, size);
}
void ImGuiLayer::onPrePaint() {
// Update ImGui input
ImGuiIO& io = ImGui::GetIO();
static double previousTime = 0.0;
double currentTime = SkTime::GetSecs();
io.DeltaTime = static_cast<float>(currentTime - previousTime);
previousTime = currentTime;
io.DisplaySize.x = static_cast<float>(fWindow->width());
io.DisplaySize.y = static_cast<float>(fWindow->height());
io.KeyAlt = io.KeysDown[static_cast<int>(Window::Key::kOption)];
io.KeyCtrl = io.KeysDown[static_cast<int>(Window::Key::kCtrl)];
io.KeyShift = io.KeysDown[static_cast<int>(Window::Key::kShift)];
ImGui::NewFrame();
}
void ImGuiLayer::onPaint(SkCanvas* canvas) {
// This causes ImGui to rebuild vertex/index data based on all immediate-mode commands
// (widgets, etc...) that have been issued
ImGui::Render();
// Then we fetch the most recent data, and convert it so we can render with Skia
const ImDrawData* drawData = ImGui::GetDrawData();
SkTDArray<SkPoint> pos;
SkTDArray<SkPoint> uv;
SkTDArray<SkColor> color;
for (int i = 0; i < drawData->CmdListsCount; ++i) {
const ImDrawList* drawList = drawData->CmdLists[i];
// De-interleave all vertex data (sigh), convert to Skia types
pos.rewind(); uv.rewind(); color.rewind();
for (int j = 0; j < drawList->VtxBuffer.size(); ++j) {
const ImDrawVert& vert = drawList->VtxBuffer[j];
pos.push_back(SkPoint::Make(vert.pos.x, vert.pos.y));
uv.push_back(SkPoint::Make(vert.uv.x, vert.uv.y));
color.push_back(vert.col);
}
// ImGui colors are RGBA
SkSwapRB(color.begin(), color.begin(), color.count());
int indexOffset = 0;
// Draw everything with canvas.drawVertices...
for (int j = 0; j < drawList->CmdBuffer.size(); ++j) {
const ImDrawCmd* drawCmd = &drawList->CmdBuffer[j];
SkAutoCanvasRestore acr(canvas, true);
// TODO: Find min/max index for each draw, so we know how many vertices (sigh)
if (drawCmd->UserCallback) {
drawCmd->UserCallback(drawList, drawCmd);
} else {
intptr_t idIndex = (intptr_t)drawCmd->TextureId;
if (idIndex < fSkiaWidgetFuncs.count()) {
// Small image IDs are actually indices into a list of callbacks. We directly
// examing the vertex data to deduce the image rectangle, then reconfigure the
// canvas to be clipped and translated so that the callback code gets to use
// Skia to render a widget in the middle of an ImGui panel.
ImDrawIdx rectIndex = drawList->IdxBuffer[indexOffset];
SkPoint tl = pos[rectIndex], br = pos[rectIndex + 2];
canvas->clipRect(SkRect::MakeLTRB(tl.fX, tl.fY, br.fX, br.fY));
canvas->translate(tl.fX, tl.fY);
fSkiaWidgetFuncs[idIndex](canvas);
} else {
SkPaint* paint = static_cast<SkPaint*>(drawCmd->TextureId);
SkASSERT(paint);
canvas->clipRect(SkRect::MakeLTRB(drawCmd->ClipRect.x, drawCmd->ClipRect.y,
drawCmd->ClipRect.z, drawCmd->ClipRect.w));
auto vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
drawList->VtxBuffer.size(),
pos.begin(), uv.begin(), color.begin(),
drawCmd->ElemCount,
drawList->IdxBuffer.begin() + indexOffset);
canvas->drawVertices(vertices, SkBlendMode::kModulate, *paint);
}
indexOffset += drawCmd->ElemCount;
}
}
}
fSkiaWidgetFuncs.reset();
}
bool ImGuiLayer::onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) {
ImGuiIO& io = ImGui::GetIO();
io.KeysDown[static_cast<int>(key)] = (Window::kDown_InputState == state);
return io.WantCaptureKeyboard;
}
bool ImGuiLayer::onChar(SkUnichar c, uint32_t modifiers) {
ImGuiIO& io = ImGui::GetIO();
if (io.WantTextInput) {
if (c > 0 && c < 0x10000) {
io.AddInputCharacter(c);
}
return true;
}
return false;
}