in tools/viewer/Viewer.cpp [2224:3218]
void Viewer::drawImGui() {
// Support drawing the ImGui demo window. Superfluous, but gives a good idea of what's possible
if (fShowImGuiTestWindow) {
ImGui::ShowDemoWindow(&fShowImGuiTestWindow);
}
if (fShowImGuiDebugWindow) {
// We have some dynamic content that sizes to fill available size. If the scroll bar isn't
// always visible, we can end up in a layout feedback loop.
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
const DisplayParams* params = fWindow->getRequestedDisplayParams();
auto newParamsBuilder = make_display_params_builder(params);
bool displayParamsChanged = false; // heavy-weight, might recreate entire context
bool uiParamsChanged = false; // light weight, just triggers window invalidation
GrDirectContext* ctx = fWindow->directContext();
if (ImGui::Begin("Tools", &fShowImGuiDebugWindow,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (ImGui::CollapsingHeader("Backend")) {
int newBackend = static_cast<int>(fBackendType);
ImGui::RadioButton("Raster", &newBackend, sk_app::Window::kRaster_BackendType);
ImGui::SameLine();
ImGui::RadioButton("OpenGL", &newBackend, sk_app::Window::kNativeGL_BackendType);
#if SK_ANGLE && (defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC))
ImGui::SameLine();
ImGui::RadioButton("ANGLE", &newBackend, sk_app::Window::kANGLE_BackendType);
#endif
#if defined(SK_DAWN)
#if defined(SK_GRAPHITE)
ImGui::SameLine();
ImGui::RadioButton("Dawn (Graphite)", &newBackend,
sk_app::Window::kGraphiteDawn_BackendType);
#endif
#endif
#if defined(SK_VULKAN) && !defined(SK_BUILD_FOR_MAC)
ImGui::SameLine();
ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType);
#if defined(SK_GRAPHITE)
ImGui::SameLine();
ImGui::RadioButton("Vulkan (Graphite)", &newBackend,
sk_app::Window::kGraphiteVulkan_BackendType);
#endif
#endif
#if defined(SK_METAL)
ImGui::SameLine();
ImGui::RadioButton("Metal", &newBackend, sk_app::Window::kMetal_BackendType);
#if defined(SK_GRAPHITE)
ImGui::SameLine();
ImGui::RadioButton("Metal (Graphite)", &newBackend,
sk_app::Window::kGraphiteMetal_BackendType);
#endif
#endif
#if defined(SK_DIRECT3D)
ImGui::SameLine();
ImGui::RadioButton("Direct3D", &newBackend, sk_app::Window::kDirect3D_BackendType);
#endif
if (newBackend != fBackendType) {
fDeferredActions.push_back([newBackend, this]() {
this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend));
});
}
if (ctx) {
GrContextOptions grOpts = params->grContextOptions();
if (ImGui::Checkbox("Wireframe Mode", &grOpts.fWireframeMode)) {
displayParamsChanged = true;
newParamsBuilder.grContextOptions(grOpts);
}
if (ImGui::Checkbox("Reduced shaders", &grOpts.fReducedShaderVariations)) {
displayParamsChanged = true;
newParamsBuilder.grContextOptions(grOpts);
}
// Determine the context's max sample count for MSAA radio buttons.
int sampleCount = fWindow->sampleCount();
int maxMSAA = (fBackendType != sk_app::Window::kRaster_BackendType) ?
ctx->maxSurfaceSampleCountForColorType(kRGBA_8888_SkColorType) :
1;
// Only display the MSAA radio buttons when there are options above 1x MSAA.
if (maxMSAA >= 4) {
ImGui::Text("MSAA: ");
for (int curMSAA = 1; curMSAA <= maxMSAA; curMSAA *= 2) {
// 2x MSAA works, but doesn't offer much of a visual improvement, so we
// don't show it in the list.
if (curMSAA == 2) {
continue;
}
ImGui::SameLine();
ImGui::RadioButton(SkStringPrintf("%d", curMSAA).c_str(),
&sampleCount, curMSAA);
}
}
if (sampleCount != params->msaaSampleCount()) {
displayParamsChanged = true;
newParamsBuilder.msaaSampleCount(sampleCount);
}
}
int pixelGeometryIdx = 0;
if (fDisplayOverrides.fSurfaceProps.fPixelGeometry) {
pixelGeometryIdx = params->surfaceProps().pixelGeometry() + 1;
}
if (ImGui::Combo("Pixel Geometry", &pixelGeometryIdx,
"Default\0Flat\0RGB\0BGR\0RGBV\0BGRV\0\0"))
{
uint32_t flags = params->surfaceProps().flags();
if (pixelGeometryIdx == 0) {
fDisplayOverrides.fSurfaceProps.fPixelGeometry = false;
SkPixelGeometry pixelGeometry = fDisplay->surfaceProps().pixelGeometry();
newParamsBuilder.surfaceProps(SkSurfaceProps(flags, pixelGeometry));
} else {
fDisplayOverrides.fSurfaceProps.fPixelGeometry = true;
SkPixelGeometry pixelGeometry = SkTo<SkPixelGeometry>(pixelGeometryIdx - 1);
newParamsBuilder.surfaceProps(SkSurfaceProps(flags, pixelGeometry));
}
displayParamsChanged = true;
}
bool useDFT = params->surfaceProps().isUseDeviceIndependentFonts();
if (ImGui::Checkbox("DFT", &useDFT)) {
uint32_t flags = params->surfaceProps().flags();
if (useDFT) {
flags |= SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
} else {
flags &= ~SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
}
SkPixelGeometry pixelGeometry = params->surfaceProps().pixelGeometry();
newParamsBuilder.surfaceProps(SkSurfaceProps(flags, pixelGeometry));
displayParamsChanged = true;
}
if (ImGui::TreeNode("Path Renderers")) {
skgpu::graphite::Context* gctx = fWindow->graphiteContext();
if (is_graphite_backend_type(fBackendType) && gctx) {
#if defined(SK_GRAPHITE)
using skgpu::graphite::PathRendererStrategy;
SkASSERT(params->graphiteTestOptions());
skwindow::GraphiteTestOptions opts = *params->graphiteTestOptions();
auto prevPrs = opts.fPriv.fPathRendererStrategy;
auto prsButton = [&](skgpu::graphite::PathRendererStrategy s) {
if (ImGui::RadioButton(get_path_renderer_strategy_string(s),
prevPrs == s)) {
if (s != opts.fPriv.fPathRendererStrategy) {
opts.fPriv.fPathRendererStrategy = s;
newParamsBuilder.graphiteTestOptions(opts);
displayParamsChanged = true;
}
}
};
prsButton(PathRendererStrategy::kDefault);
PathRendererStrategy strategies[] = {
PathRendererStrategy::kComputeAnalyticAA,
PathRendererStrategy::kComputeMSAA16,
PathRendererStrategy::kComputeMSAA8,
PathRendererStrategy::kRasterAA,
PathRendererStrategy::kTessellation,
};
for (size_t i = 0; i < std::size(strategies); ++i) {
if (gctx->priv().supportsPathRendererStrategy(strategies[i])) {
prsButton(strategies[i]);
}
}
#endif
} else if (ctx) {
GrContextOptions grOpts = params->grContextOptions();
auto prButton = [&](GpuPathRenderers x) {
if (ImGui::RadioButton(gGaneshPathRendererNames[x].c_str(),
grOpts.fGpuPathRenderers == x)) {
if (x != grOpts.fGpuPathRenderers) {
grOpts.fGpuPathRenderers = x;
displayParamsChanged = true;
newParamsBuilder.grContextOptions(grOpts);
}
}
};
prButton(GpuPathRenderers::kDefault);
#if defined(SK_GANESH)
if (fWindow->sampleCount() > 1 || FLAGS_dmsaa) {
const auto* caps = ctx->priv().caps();
if (skgpu::ganesh::AtlasPathRenderer::IsSupported(ctx)) {
prButton(GpuPathRenderers::kAtlas);
}
if (skgpu::ganesh::TessellationPathRenderer::IsSupported(*caps)) {
prButton(GpuPathRenderers::kTessellation);
}
}
#endif
if (1 == fWindow->sampleCount()) {
prButton(GpuPathRenderers::kSmall);
}
prButton(GpuPathRenderers::kTriangulating);
prButton(GpuPathRenderers::kNone);
} else {
ImGui::RadioButton("Software", true);
}
ImGui::TreePop();
}
}
if (ImGui::CollapsingHeader("Tiling")) {
ImGui::Checkbox("Enable", &fTiled);
ImGui::Checkbox("Draw Boundaries", &fDrawTileBoundaries);
ImGui::SliderFloat("Horizontal", &fTileScale.fWidth, 0.1f, 1.0f);
ImGui::SliderFloat("Vertical", &fTileScale.fHeight, 0.1f, 1.0f);
}
if (ImGui::CollapsingHeader("Transform")) {
if (ImGui::Checkbox("Apply Backing Scale", &fApplyBackingScale)) {
this->updateGestureTransLimit();
// NOTE: Do not call onResize() as we are in the middle of ImgGui processing,
// and onResize() modifies ImgGui state that is otherwise locked. The backing
// scale factor only affects the slide itself, so adjust that directly.
this->resizeCurrentSlide(fWindow->width(), fWindow->height());
// This changes how we manipulate the canvas transform, it's not changing the
// window's actual parameters.
uiParamsChanged = true;
}
float zoom = fZoomLevel;
if (ImGui::SliderFloat("Zoom", &zoom, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
fZoomLevel = zoom;
this->updateGestureTransLimit();
uiParamsChanged = true;
}
float deg = fRotation;
if (ImGui::SliderFloat("Rotate", °, -30, 360, "%.3f deg")) {
fRotation = deg;
this->updateGestureTransLimit();
uiParamsChanged = true;
}
if (ImGui::CollapsingHeader("Subpixel offset", ImGuiTreeNodeFlags_NoTreePushOnOpen)) {
if (ImGui_DragLocation(&fOffset)) {
this->updateGestureTransLimit();
uiParamsChanged = true;
}
} else if (fOffset != SkVector{0.5f, 0.5f}) {
this->updateGestureTransLimit();
uiParamsChanged = true;
fOffset = {0.5f, 0.5f};
}
int perspectiveMode = static_cast<int>(fPerspectiveMode);
if (ImGui::Combo("Perspective", &perspectiveMode, "Off\0Real\0Fake\0\0")) {
fPerspectiveMode = static_cast<PerspectiveMode>(perspectiveMode);
this->updateGestureTransLimit();
uiParamsChanged = true;
}
if (perspectiveMode != kPerspective_Off && ImGui_DragQuad(fPerspectivePoints)) {
this->updateGestureTransLimit();
uiParamsChanged = true;
}
}
if (ImGui::CollapsingHeader("Paint")) {
auto paintFlag = [this, &uiParamsChanged](const char* label, const char* items,
bool SkPaintFields::* flag,
bool (SkPaint::* isFlag)() const,
void (SkPaint::* setFlag)(bool) )
{
int itemIndex = 0;
if (fPaintOverrides.*flag) {
itemIndex = (fPaint.*isFlag)() ? 2 : 1;
}
if (ImGui::Combo(label, &itemIndex, items)) {
if (itemIndex == 0) {
fPaintOverrides.*flag = false;
} else {
fPaintOverrides.*flag = true;
(fPaint.*setFlag)(itemIndex == 2);
}
uiParamsChanged = true;
}
};
paintFlag("Antialias",
"Default\0No AA\0AA\0\0",
&SkPaintFields::fAntiAlias,
&SkPaint::isAntiAlias, &SkPaint::setAntiAlias);
paintFlag("Dither",
"Default\0No Dither\0Dither\0\0",
&SkPaintFields::fDither,
&SkPaint::isDither, &SkPaint::setDither);
int styleIdx = 0;
if (fPaintOverrides.fStyle) {
styleIdx = SkTo<int>(fPaint.getStyle()) + 1;
}
if (ImGui::Combo("Style", &styleIdx,
"Default\0Fill\0Stroke\0Stroke and Fill\0\0"))
{
if (styleIdx == 0) {
fPaintOverrides.fStyle = false;
fPaint.setStyle(SkPaint::kFill_Style);
} else {
fPaint.setStyle(SkTo<SkPaint::Style>(styleIdx - 1));
fPaintOverrides.fStyle = true;
}
uiParamsChanged = true;
}
ImGui::Checkbox("Force Runtime Blends", &fPaintOverrides.fForceRuntimeBlend);
ImGui::Checkbox("Override Stroke Width", &fPaintOverrides.fStrokeWidth);
if (fPaintOverrides.fStrokeWidth) {
float width = fPaint.getStrokeWidth();
if (ImGui::SliderFloat("Stroke Width", &width, 0, 20)) {
fPaint.setStrokeWidth(width);
uiParamsChanged = true;
}
}
ImGui::Checkbox("Override Miter Limit", &fPaintOverrides.fMiterLimit);
if (fPaintOverrides.fMiterLimit) {
float miterLimit = fPaint.getStrokeMiter();
if (ImGui::SliderFloat("Miter Limit", &miterLimit, 0, 20)) {
fPaint.setStrokeMiter(miterLimit);
uiParamsChanged = true;
}
}
int capIdx = 0;
if (fPaintOverrides.fCapType) {
capIdx = SkTo<int>(fPaint.getStrokeCap()) + 1;
}
if (ImGui::Combo("Cap Type", &capIdx,
"Default\0Butt\0Round\0Square\0\0"))
{
if (capIdx == 0) {
fPaintOverrides.fCapType = false;
fPaint.setStrokeCap(SkPaint::kDefault_Cap);
} else {
fPaint.setStrokeCap(SkTo<SkPaint::Cap>(capIdx - 1));
fPaintOverrides.fCapType = true;
}
uiParamsChanged = true;
}
int joinIdx = 0;
if (fPaintOverrides.fJoinType) {
joinIdx = SkTo<int>(fPaint.getStrokeJoin()) + 1;
}
if (ImGui::Combo("Join Type", &joinIdx,
"Default\0Miter\0Round\0Bevel\0\0"))
{
if (joinIdx == 0) {
fPaintOverrides.fJoinType = false;
fPaint.setStrokeJoin(SkPaint::kDefault_Join);
} else {
fPaint.setStrokeJoin(SkTo<SkPaint::Join>(joinIdx - 1));
fPaintOverrides.fJoinType = true;
}
uiParamsChanged = true;
}
}
if (ImGui::CollapsingHeader("Font")) {
int hintingIdx = 0;
if (fFontOverrides.fHinting) {
hintingIdx = SkTo<int>(fFont.getHinting()) + 1;
}
if (ImGui::Combo("Hinting", &hintingIdx,
"Default\0None\0Slight\0Normal\0Full\0\0"))
{
if (hintingIdx == 0) {
fFontOverrides.fHinting = false;
fFont.setHinting(SkFontHinting::kNone);
} else {
fFont.setHinting(SkTo<SkFontHinting>(hintingIdx - 1));
fFontOverrides.fHinting = true;
}
uiParamsChanged = true;
}
auto fontFlag = [this, &uiParamsChanged](const char* label, const char* items,
bool SkFontFields::* flag,
bool (SkFont::* isFlag)() const,
void (SkFont::* setFlag)(bool) )
{
int itemIndex = 0;
if (fFontOverrides.*flag) {
itemIndex = (fFont.*isFlag)() ? 2 : 1;
}
if (ImGui::Combo(label, &itemIndex, items)) {
if (itemIndex == 0) {
fFontOverrides.*flag = false;
} else {
fFontOverrides.*flag = true;
(fFont.*setFlag)(itemIndex == 2);
}
uiParamsChanged = true;
}
};
fontFlag("Fake Bold Glyphs",
"Default\0No Fake Bold\0Fake Bold\0\0",
&SkFontFields::fEmbolden,
&SkFont::isEmbolden, &SkFont::setEmbolden);
fontFlag("Baseline Snapping",
"Default\0No Baseline Snapping\0Baseline Snapping\0\0",
&SkFontFields::fBaselineSnap,
&SkFont::isBaselineSnap, &SkFont::setBaselineSnap);
fontFlag("Linear Text",
"Default\0No Linear Text\0Linear Text\0\0",
&SkFontFields::fLinearMetrics,
&SkFont::isLinearMetrics, &SkFont::setLinearMetrics);
fontFlag("Subpixel Position Glyphs",
"Default\0Pixel Text\0Subpixel Text\0\0",
&SkFontFields::fSubpixel,
&SkFont::isSubpixel, &SkFont::setSubpixel);
fontFlag("Embedded Bitmap Text",
"Default\0No Embedded Bitmaps\0Embedded Bitmaps\0\0",
&SkFontFields::fEmbeddedBitmaps,
&SkFont::isEmbeddedBitmaps, &SkFont::setEmbeddedBitmaps);
fontFlag("Force Auto-Hinting",
"Default\0No Force Auto-Hinting\0Force Auto-Hinting\0\0",
&SkFontFields::fForceAutoHinting,
&SkFont::isForceAutoHinting, &SkFont::setForceAutoHinting);
int edgingIdx = 0;
if (fFontOverrides.fEdging) {
edgingIdx = SkTo<int>(fFont.getEdging()) + 1;
}
if (ImGui::Combo("Edging", &edgingIdx,
"Default\0Alias\0Antialias\0Subpixel Antialias\0\0"))
{
if (edgingIdx == 0) {
fFontOverrides.fEdging = false;
fFont.setEdging(SkFont::Edging::kAlias);
} else {
fFont.setEdging(SkTo<SkFont::Edging>(edgingIdx-1));
fFontOverrides.fEdging = true;
}
uiParamsChanged = true;
}
ImGui::Checkbox("Override Size", &fFontOverrides.fSize);
if (fFontOverrides.fSize) {
ImGui::DragFloat2("TextRange", fFontOverrides.fSizeRange,
0.001f, -10.0f, 300.0f, "%.6f", ImGuiSliderFlags_Logarithmic);
float textSize = fFont.getSize();
if (ImGui::DragFloat("TextSize", &textSize, 0.001f,
fFontOverrides.fSizeRange[0],
fFontOverrides.fSizeRange[1],
"%.6f", ImGuiSliderFlags_Logarithmic))
{
fFont.setSize(textSize);
uiParamsChanged = true;
}
}
ImGui::Checkbox("Override ScaleX", &fFontOverrides.fScaleX);
if (fFontOverrides.fScaleX) {
float scaleX = fFont.getScaleX();
if (ImGui::SliderFloat("ScaleX", &scaleX, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
fFont.setScaleX(scaleX);
uiParamsChanged = true;
}
}
ImGui::Checkbox("Override SkewX", &fFontOverrides.fSkewX);
if (fFontOverrides.fSkewX) {
float skewX = fFont.getSkewX();
if (ImGui::SliderFloat("SkewX", &skewX, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
fFont.setSkewX(skewX);
uiParamsChanged = true;
}
}
}
{
SkMetaData controls;
if (fSlides[fCurrentSlide]->onGetControls(&controls)) {
if (ImGui::CollapsingHeader("Current Slide")) {
SkMetaData::Iter iter(controls);
const char* name;
SkMetaData::Type type;
int count;
while ((name = iter.next(&type, &count)) != nullptr) {
if (type == SkMetaData::kScalar_Type) {
float val[3];
SkASSERT(count == 3);
controls.findScalars(name, &count, val);
if (ImGui::SliderFloat(name, &val[0], val[1], val[2])) {
controls.setScalars(name, 3, val);
}
} else if (type == SkMetaData::kBool_Type) {
bool val;
SkASSERT(count == 1);
controls.findBool(name, &val);
if (ImGui::Checkbox(name, &val)) {
controls.setBool(name, val);
}
}
}
fSlides[fCurrentSlide]->onSetControls(controls);
}
}
}
if (fShowSlidePicker) {
ImGui::SetNextTreeNodeOpen(true);
}
if (ImGui::CollapsingHeader("Slide")) {
static ImGuiTextFilter filter;
static ImVector<const char*> filteredSlideNames;
static ImVector<int> filteredSlideIndices;
if (fShowSlidePicker) {
ImGui::SetKeyboardFocusHere();
fShowSlidePicker = false;
}
filter.Draw();
filteredSlideNames.clear();
filteredSlideIndices.clear();
int filteredIndex = 0;
for (int i = 0; i < fSlides.size(); ++i) {
const char* slideName = fSlides[i]->getName().c_str();
if (filter.PassFilter(slideName) || i == fCurrentSlide) {
if (i == fCurrentSlide) {
filteredIndex = filteredSlideIndices.size();
}
filteredSlideNames.push_back(slideName);
filteredSlideIndices.push_back(i);
}
}
if (ImGui::ListBox("", &filteredIndex, filteredSlideNames.begin(),
filteredSlideNames.size(), 20)) {
this->setCurrentSlide(filteredSlideIndices[filteredIndex]);
}
}
if (ImGui::CollapsingHeader("Color Mode")) {
ColorMode newMode = fColorMode;
auto cmButton = [&](ColorMode mode, const char* label) {
if (ImGui::RadioButton(label, mode == fColorMode)) {
newMode = mode;
}
};
cmButton(ColorMode::kLegacy, "Legacy 8888");
cmButton(ColorMode::kColorManaged8888, "Color Managed 8888");
cmButton(ColorMode::kColorManagedF16, "Color Managed F16");
cmButton(ColorMode::kColorManagedF16Norm, "Color Managed F16 Norm");
if (newMode != fColorMode) {
this->setColorMode(newMode);
}
// Pick from common gamuts:
int primariesIdx = 4; // Default: Custom
for (size_t i = 0; i < std::size(gNamedPrimaries); ++i) {
if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
primariesIdx = i;
break;
}
}
// Let user adjust the gamma
ImGui::SliderFloat("Gamma", &fColorSpaceTransferFn.g, 0.5f, 3.5f);
if (ImGui::Combo("Primaries", &primariesIdx,
"sRGB\0AdobeRGB\0P3\0Rec. 2020\0Custom\0\0")) {
if (primariesIdx >= 0 && primariesIdx <= 3) {
fColorSpacePrimaries = *gNamedPrimaries[primariesIdx].fPrimaries;
}
}
if (ImGui::Button("Spin")) {
float rx = fColorSpacePrimaries.fRX,
ry = fColorSpacePrimaries.fRY;
fColorSpacePrimaries.fRX = fColorSpacePrimaries.fGX;
fColorSpacePrimaries.fRY = fColorSpacePrimaries.fGY;
fColorSpacePrimaries.fGX = fColorSpacePrimaries.fBX;
fColorSpacePrimaries.fGY = fColorSpacePrimaries.fBY;
fColorSpacePrimaries.fBX = rx;
fColorSpacePrimaries.fBY = ry;
}
// Allow direct editing of gamut
ImGui_Primaries(&fColorSpacePrimaries, &fImGuiGamutPaint);
}
if (ImGui::CollapsingHeader("Animation")) {
bool isPaused = AnimTimer::kPaused_State == fAnimTimer.state();
if (ImGui::Checkbox("Pause", &isPaused)) {
fAnimTimer.togglePauseResume();
}
float speed = fAnimTimer.getSpeed();
if (ImGui::DragFloat("Speed", &speed, 0.1f)) {
fAnimTimer.setSpeed(speed);
}
}
if (ImGui::CollapsingHeader("Shaders")) {
bool sksl = params->grContextOptions().fShaderCacheStrategy ==
GrContextOptions::ShaderCacheStrategy::kSkSL;
const bool isVulkan = fBackendType == sk_app::Window::kVulkan_BackendType;
// To re-load shaders from the currently active programs, we flush all
// caches on one frame, then set a flag to poll the cache on the next frame.
static bool gLoadPending = false;
if (gLoadPending) {
fCachedShaders.clear();
if (ctx) {
fPersistentCache.foreach([this](sk_sp<const SkData> key,
sk_sp<SkData> data,
const SkString& description,
int hitCount) {
CachedShader& entry(fCachedShaders.push_back());
entry.fKey = key;
SkMD5 hash;
hash.write(key->bytes(), key->size());
entry.fKeyString = hash.finish().toHexString();
entry.fKeyDescription = description;
SkReadBuffer reader(data->data(), data->size());
entry.fShaderType = GrPersistentCacheUtils::GetType(&reader);
GrPersistentCacheUtils::UnpackCachedShaders(&reader, entry.fShader,
entry.fInterfaces,
kGrShaderTypeCount);
});
}
#if defined(SK_GRAPHITE)
if (skgpu::graphite::Context* gctx = fWindow->graphiteContext()) {
int index = 1;
auto callback = [&](const skgpu::UniqueKey& key,
const skgpu::graphite::GraphicsPipeline* pipeline) {
// Retrieve the shaders from the pipeline.
const skgpu::graphite::GraphicsPipeline::PipelineInfo& pipelineInfo =
pipeline->getPipelineInfo();
CachedShader& entry(fCachedShaders.push_back());
entry.fKey = nullptr;
entry.fKeyString = SkStringPrintf("#%-3d %s",
index++, pipelineInfo.fLabel.c_str());
if (sksl) {
entry.fShader[kVertex_GrShaderType] =
pipelineInfo.fSkSLVertexShader;
entry.fShader[kFragment_GrShaderType] =
pipelineInfo.fSkSLFragmentShader;
entry.fShaderType = SkSetFourByteTag('S', 'K', 'S', 'L');
} else {
entry.fShader[kVertex_GrShaderType] =
pipelineInfo.fNativeVertexShader;
entry.fShader[kFragment_GrShaderType] =
pipelineInfo.fNativeFragmentShader;
// We could derive the shader type from the GraphicsPipeline's type
// if there is ever a need to.
entry.fShaderType = SkSetFourByteTag('?', '?', '?', '?');
}
};
gctx->priv().globalCache()->forEachGraphicsPipeline(callback);
}
#endif
gLoadPending = false;
#if defined(SK_VULKAN)
if (isVulkan && !sksl) {
// Disassemble the SPIR-V into its textual form.
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
for (auto& entry : fCachedShaders) {
for (int i = 0; i < kGrShaderTypeCount; ++i) {
const std::string& spirv(entry.fShader[i]);
std::string disasm;
tools.Disassemble((const uint32_t*)spirv.c_str(), spirv.size() / 4,
&disasm);
entry.fShader[i].assign(disasm);
}
}
} else
#endif
{
// Reformat the SkSL with proper indentation.
for (auto& entry : fCachedShaders) {
for (int i = 0; i < kGrShaderTypeCount; ++i) {
entry.fShader[i] = SkShaderUtils::PrettyPrint(entry.fShader[i]);
}
}
}
}
// Defer actually doing the View/Apply logic so that we can trigger an Apply when we
// start or finish hovering on a tree node in the list below:
bool doView = ImGui::Button("View"); ImGui::SameLine();
bool doApply = false;
bool doDump = false;
if (ctx) {
// TODO(skia:14418): we only have Ganesh implementations of Apply/Dump
doApply = ImGui::Button("Apply Changes"); ImGui::SameLine();
doDump = ImGui::Button("Dump SkSL to resources/sksl/");
}
int newOptLevel = fOptLevel;
ImGui::RadioButton("SkSL", &newOptLevel, kShaderOptLevel_Source);
ImGui::SameLine();
ImGui::RadioButton("Compile", &newOptLevel, kShaderOptLevel_Compile);
ImGui::SameLine();
ImGui::RadioButton("Optimize", &newOptLevel, kShaderOptLevel_Optimize);
ImGui::SameLine();
ImGui::RadioButton("Inline", &newOptLevel, kShaderOptLevel_Inline);
// If we are changing the compile mode, we want to reset the cache and redo
// everything.
static bool sDoDeferredView = false;
if (doView || doDump || newOptLevel != fOptLevel) {
sksl = doDump || (newOptLevel == kShaderOptLevel_Source);
fOptLevel = (ShaderOptLevel)newOptLevel;
switch (fOptLevel) {
case kShaderOptLevel_Source:
Compiler::EnableOptimizer(OverrideFlag::kOff);
Compiler::EnableInliner(OverrideFlag::kOff);
break;
case kShaderOptLevel_Compile:
Compiler::EnableOptimizer(OverrideFlag::kOff);
Compiler::EnableInliner(OverrideFlag::kOff);
break;
case kShaderOptLevel_Optimize:
Compiler::EnableOptimizer(OverrideFlag::kOn);
Compiler::EnableInliner(OverrideFlag::kOff);
break;
case kShaderOptLevel_Inline:
Compiler::EnableOptimizer(OverrideFlag::kOn);
Compiler::EnableInliner(OverrideFlag::kOn);
break;
}
GrContextOptions grOpts = params->grContextOptions();
grOpts.fShaderCacheStrategy =
sksl ? GrContextOptions::ShaderCacheStrategy::kSkSL
: GrContextOptions::ShaderCacheStrategy::kBackendSource;
displayParamsChanged = true;
newParamsBuilder.grContextOptions(grOpts);
fDeferredActions.push_back([doDump, this]() {
// Reset the cache.
fPersistentCache.reset();
sDoDeferredView = true;
// Dump the cache once we have drawn a frame with it.
if (doDump) {
fDeferredActions.push_back([this]() {
this->dumpShadersToResources();
});
}
});
}
ImGui::BeginChild("##ScrollingRegion");
for (auto& entry : fCachedShaders) {
bool inTreeNode = ImGui::TreeNode(entry.fKeyString.c_str());
bool hovered = ImGui::IsItemHovered();
if (hovered != entry.fHovered) {
// Force an Apply to patch the highlight shader in/out
entry.fHovered = hovered;
doApply = true;
}
if (inTreeNode) {
auto stringBox = [](const char* label, std::string* str) {
// Full width, and not too much space for each shader
int lines = std::count(str->begin(), str->end(), '\n') + 2;
ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * std::min(lines, 30));
ImGui::InputTextMultiline(label, str, boxSize);
};
if (ImGui::TreeNode("Key")) {
ImGui::TextWrapped("%s", entry.fKeyDescription.c_str());
ImGui::TreePop();
}
stringBox("##VP", &entry.fShader[kVertex_GrShaderType]);
stringBox("##FP", &entry.fShader[kFragment_GrShaderType]);
ImGui::TreePop();
}
}
ImGui::EndChild();
if (doView || sDoDeferredView) {
fPersistentCache.reset();
if (ctx) {
ctx->priv().getGpu()->resetShaderCacheForTesting();
}
#if defined(SK_GRAPHITE)
if (skgpu::graphite::Context* gctx = fWindow->graphiteContext()) {
gctx->priv().globalCache()->deleteResources();
}
#endif
gLoadPending = true;
sDoDeferredView = false;
}
// We don't support updating SPIRV shaders. We could re-assemble them (with edits),
// but I'm not sure anyone wants to do that.
if (isVulkan && !sksl) {
doApply = false;
}
if (ctx && doApply) {
fPersistentCache.reset();
ctx->priv().getGpu()->resetShaderCacheForTesting();
for (auto& entry : fCachedShaders) {
std::string backup = entry.fShader[kFragment_GrShaderType];
if (entry.fHovered) {
// The hovered item (if any) gets a special shader to make it
// identifiable.
std::string& fragShader = entry.fShader[kFragment_GrShaderType];
switch (entry.fShaderType) {
case SkSetFourByteTag('S', 'K', 'S', 'L'): {
fragShader = build_sksl_highlight_shader();
break;
}
case SkSetFourByteTag('G', 'L', 'S', 'L'): {
fragShader = build_glsl_highlight_shader(
*ctx->priv().caps()->shaderCaps());
break;
}
case SkSetFourByteTag('M', 'S', 'L', ' '): {
fragShader = build_metal_highlight_shader(fragShader);
break;
}
}
}
auto data = GrPersistentCacheUtils::PackCachedShaders(entry.fShaderType,
entry.fShader,
entry.fInterfaces,
kGrShaderTypeCount);
fPersistentCache.store(*entry.fKey, *data, entry.fKeyDescription);
entry.fShader[kFragment_GrShaderType] = backup;
}
}
}
}
if (displayParamsChanged || uiParamsChanged) {
// The unique_ptr cannot be captured, so release it and capture the raw pointer value in
// the lambda. The lambda is responsible for deleting it, which will only be called once
skwindow::DisplayParams* newParams = newParamsBuilder.build().release();
fDeferredActions.emplace_back([displayParamsChanged, newParams, this]() {
if (displayParamsChanged) {
fWindow->setRequestedDisplayParams(
std::unique_ptr<skwindow::DisplayParams>(newParams));
}
fWindow->inval();
this->updateTitle();
});
}
ImGui::End();
}
if (gShaderErrorHandler.fErrors.size()) {
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
ImGui::Begin("Shader Errors", nullptr, ImGuiWindowFlags_NoFocusOnAppearing);
for (int i = 0; i < gShaderErrorHandler.fErrors.size(); ++i) {
ImGui::TextWrapped("%s", gShaderErrorHandler.fErrors[i].c_str());
std::string sksl(gShaderErrorHandler.fShaders[i].c_str());
SkShaderUtils::VisitLineByLine(sksl, [](int lineNumber, const char* lineText) {
ImGui::TextWrapped("%4i\t%s\n", lineNumber, lineText);
});
}
ImGui::End();
gShaderErrorHandler.reset();
}
if (fShowZoomWindow && fLastImage) {
ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Zoom", &fShowZoomWindow)) {
static int zoomFactor = 8;
if (ImGui::Button("<<")) {
zoomFactor = std::max(zoomFactor / 2, 4);
}
ImGui::SameLine(); ImGui::Text("%2d", zoomFactor); ImGui::SameLine();
if (ImGui::Button(">>")) {
zoomFactor = std::min(zoomFactor * 2, 32);
}
if (!fZoomWindowFixed) {
ImVec2 mousePos = ImGui::GetMousePos();
fZoomWindowLocation = SkPoint::Make(mousePos.x, mousePos.y);
}
SkScalar x = fZoomWindowLocation.x();
SkScalar y = fZoomWindowLocation.y();
int xInt = SkScalarRoundToInt(x);
int yInt = SkScalarRoundToInt(y);
ImVec2 avail = ImGui::GetContentRegionAvail();
uint32_t pixel = 0;
SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
bool didGraphiteRead = false;
if (is_graphite_backend_type(fBackendType)) {
#if defined(GPU_TEST_UTILS)
SkBitmap bitmap;
bitmap.allocPixels(info);
SkPixmap pixels;
SkAssertResult(bitmap.peekPixels(&pixels));
didGraphiteRead = as_IB(fLastImage)
->readPixelsGraphite(
fWindow->graphiteRecorder(), pixels, xInt, yInt);
pixel = *pixels.addr32();
ImGui::SameLine();
ImGui::Text("(X, Y): %d, %d RGBA: %X %X %X %X",
xInt, yInt,
SkGetPackedR32(pixel), SkGetPackedG32(pixel),
SkGetPackedB32(pixel), SkGetPackedA32(pixel));
#endif
}
auto dContext = fWindow->directContext();
if (fLastImage->readPixels(dContext,
info,
&pixel,
info.minRowBytes(),
xInt,
yInt)) {
ImGui::SameLine();
ImGui::Text("(X, Y): %d, %d RGBA: %X %X %X %X",
xInt, yInt,
SkGetPackedR32(pixel), SkGetPackedG32(pixel),
SkGetPackedB32(pixel), SkGetPackedA32(pixel));
} else {
if (!didGraphiteRead) {
ImGui::SameLine();
ImGui::Text("Failed to readPixels");
}
}
fImGuiLayer.skiaWidget(avail, [=, lastImage = fLastImage](SkCanvas* c) {
// Translate so the region of the image that's under the mouse cursor is centered
// in the zoom canvas:
c->scale(zoomFactor, zoomFactor);
c->translate(avail.x * 0.5f / zoomFactor - x - 0.5f,
avail.y * 0.5f / zoomFactor - y - 0.5f);
c->drawImage(lastImage, 0, 0);
// Draw a pixel outline around the pixel whose color and coordinate are displayed
// in the text of the widget. The paint is configured to ensure contrast on any
// background color.
SkPaint outline;
outline.setColor(SK_ColorWHITE);
outline.setStyle(SkPaint::kStroke_Style);
outline.setBlendMode(SkBlendMode::kExclusion);
c->drawRect(SkRect::MakeXYWH(x, y, 1, 1), outline);
});
}
ImGui::End();
}
if (fShowHistogramWindow && fLastImage) {
ImGui::SetNextWindowSize(ImVec2(450, 500));
ImGui::SetNextWindowBgAlpha(0.5f);
if (ImGui::Begin("Color Histogram (R,G,B)", &fShowHistogramWindow)) {
const auto info = SkImageInfo::MakeN32Premul(fWindow->width(), fWindow->height());
SkAutoPixmapStorage pixmap;
pixmap.alloc(info);
if (fLastImage->readPixels(fWindow->directContext(), info, pixmap.writable_addr(),
info.minRowBytes(), 0, 0)) {
std::vector<float> r(256), g(256), b(256);
for (int y = 0; y < info.height(); ++y) {
for (int x = 0; x < info.width(); ++x) {
const auto pmc = *pixmap.addr32(x, y);
r[SkGetPackedR32(pmc)]++;
g[SkGetPackedG32(pmc)]++;
b[SkGetPackedB32(pmc)]++;
}
}
ImGui::PushItemWidth(-1);
ImGui::PlotHistogram("R", r.data(), r.size(), 0, nullptr,
FLT_MAX, FLT_MAX, ImVec2(0, 150));
ImGui::PlotHistogram("G", g.data(), g.size(), 0, nullptr,
FLT_MAX, FLT_MAX, ImVec2(0, 150));
ImGui::PlotHistogram("B", b.data(), b.size(), 0, nullptr,
FLT_MAX, FLT_MAX, ImVec2(0, 150));
ImGui::PopItemWidth();
}
}
ImGui::End();
}
}