#if defined(__linux__) #define RENDERDOC_WINDOWING_XLIB 1 #define RENDERDOC_WINDOWING_XCB 1 #endif #include "TextureViewer.h" #include "Code/Core.h" #include "FlowLayout.h" #include "ui_TextureViewer.h" #if defined(__linux__) #include #include #include #include #endif struct Formatter { static QString Format(float f) { return QString::number(f); } static QString Format(double d) { return QString::number(d); } static QString Format(uint32_t u) { return QString::number(u); } static QString Format(uint16_t u) { return QString::number(u); } static QString Format(int32_t i) { return QString::number(i); } }; TextureViewer::TextureViewer(Core *core, QWidget *parent) : QFrame(parent), ui(new Ui::TextureViewer), m_Core(core) { ui->setupUi(this); m_Core->AddLogViewer(this); ui->render->SetOutput(m_Core, NULL); ui->pixelContext->SetOutput(m_Core, NULL); m_PickedPoint = QPoint(-1, -1); QWidget *renderContainer = ui->renderContainer; QObject::connect(ui->render, &CustomPaintWidget::clicked, this, &TextureViewer::render_mouseClick); QObject::connect(ui->render, &CustomPaintWidget::mouseMove, this, &TextureViewer::render_mouseMove); QObject::connect(ui->render, &CustomPaintWidget::mouseWheel, this, &TextureViewer::render_mouseWheel); QObject::connect(ui->render, &CustomPaintWidget::resize, this, &TextureViewer::render_resize); QObject::connect(ui->zoomOption->lineEdit(), &QLineEdit::returnPressed, this, &TextureViewer::on_zoomOption_returnPressed); ui->dockarea->addToolWindow(ui->renderContainer, ToolWindowManager::EmptySpace); ui->dockarea->setToolWindowProperties(renderContainer, ToolWindowManager::DisallowUserDocking | ToolWindowManager::HideCloseButton | ToolWindowManager::DisableDraggableTab); ToolWindowManager::AreaReference ref(ToolWindowManager::AddTo, ui->dockarea->areaOf(renderContainer)); /* QWidget *lockedTabTest = new QWidget(this); lockedTabTest->setWindowTitle(tr("Locked Tab #1")); ui->dockarea->addToolWindow(lockedTabTest, ref); ui->dockarea->setToolWindowProperties(lockedTabTest, ToolWindowManager::DisallowUserDocking | ToolWindowManager::HideCloseButton); lockedTabTest = new QWidget(this); lockedTabTest->setWindowTitle(tr("Locked Tab #2")); ui->dockarea->addToolWindow(lockedTabTest, ref); ui->dockarea->setToolWindowProperties(lockedTabTest, ToolWindowManager::DisallowUserDocking | ToolWindowManager::HideCloseButton); lockedTabTest = new QWidget(this); lockedTabTest->setWindowTitle(tr("Locked Tab #3")); ui->dockarea->addToolWindow(lockedTabTest, ref); ui->dockarea->setToolWindowProperties(lockedTabTest, ToolWindowManager::DisallowUserDocking | ToolWindowManager::HideCloseButton); lockedTabTest = new QWidget(this); lockedTabTest->setWindowTitle(tr("Locked Tab #4")); ui->dockarea->addToolWindow(lockedTabTest, ref); ui->dockarea->setToolWindowProperties(lockedTabTest, ToolWindowManager::DisallowUserDocking | ToolWindowManager::HideCloseButton);*/ ui->dockarea->addToolWindow(ui->resourceThumbs, ToolWindowManager::AreaReference( ToolWindowManager::RightOf, ui->dockarea->areaOf(renderContainer), 0.25f)); ui->dockarea->setToolWindowProperties(ui->resourceThumbs, ToolWindowManager::HideCloseButton); ui->dockarea->addToolWindow( ui->targetThumbs, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, ui->dockarea->areaOf(ui->resourceThumbs))); ui->dockarea->setToolWindowProperties(ui->targetThumbs, ToolWindowManager::HideCloseButton); // need to add a way to make this less than 50% programmatically ui->dockarea->addToolWindow( ui->pixelContextLayout, ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, ui->dockarea->areaOf(ui->targetThumbs), 0.25f)); ui->dockarea->setToolWindowProperties(ui->pixelContextLayout, ToolWindowManager::HideCloseButton); ui->dockarea->setAllowFloatingWindow(false); ui->dockarea->setRubberBandLineWidth(50); renderContainer->setWindowTitle(tr("OM RenderTarget 0 - GBuffer Colour")); ui->pixelContextLayout->setWindowTitle(tr("Pixel Context")); ui->targetThumbs->setWindowTitle(tr("OM Targets")); ui->resourceThumbs->setWindowTitle(tr("PS Resources")); QVBoxLayout *vertical = new QVBoxLayout(this); vertical->setSpacing(3); vertical->setContentsMargins(0, 0, 0, 0); QWidget *flow1widget = new QWidget(this); QWidget *flow2widget = new QWidget(this); FlowLayout *flow1 = new FlowLayout(flow1widget, 0, 3, 3); FlowLayout *flow2 = new FlowLayout(flow2widget, 0, 3, 3); flow1widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); flow2widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); flow1->addWidget(ui->channelsToolbar); flow1->addWidget(ui->subresourceToolbar); flow1->addWidget(ui->actionToolbar); flow2->addWidget(ui->zoomToolbar); flow2->addWidget(ui->overlayToolbar); flow2->addWidget(ui->rangeToolbar); vertical->addWidget(flow1widget); vertical->addWidget(flow2widget); vertical->addWidget(ui->dockarea); Ui_TextureViewer *u = ui; u->pixelcontextgrid->setAlignment(u->pushButton, Qt::AlignCenter); u->pixelcontextgrid->setAlignment(u->pushButton_2, Qt::AlignCenter); QWidget *statusflowWidget = new QWidget(this); FlowLayout *statusflow = new FlowLayout(statusflowWidget, 0, 3, 0); statusflowWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); ui->statusbar->removeWidget(ui->texStatusDim); ui->statusbar->removeWidget(ui->pickSwatch); ui->statusbar->removeWidget(ui->statusText); statusflow->addWidget(ui->texStatusDim); statusflow->addWidget(ui->pickSwatch); statusflow->addWidget(ui->statusText); ui->statusbar->addWidget(statusflowWidget); ui->zoomOption->addItems({"10%", "25%", "50%", "75%", "100%", "200%", "400%", "800%"}); ui->zoomOption->setCurrentText(""); ui->fitToWindow->toggle(); UI_UpdateTextureDetails(); } TextureViewer::~TextureViewer() { m_Core->RemoveLogViewer(this); delete ui; } void TextureViewer::RT_FetchCurrentPixel(uint32_t x, uint32_t y, PixelValue &pickValue, PixelValue &realValue) { // FetchTexture tex = CurrentTexture; // if (tex == null) return; // if (m_TexDisplay.FlipY) // y = (tex.height - 1) - y; m_Output->PickPixel(m_TexDisplay.texid, true, x, y, m_TexDisplay.sliceFace, m_TexDisplay.mip, m_TexDisplay.sampleIdx, &pickValue); if(m_TexDisplay.CustomShader != ResourceId()) m_Output->PickPixel(m_TexDisplay.texid, false, x, y, m_TexDisplay.sliceFace, m_TexDisplay.mip, m_TexDisplay.sampleIdx, &realValue); } void TextureViewer::RT_PickPixelsAndUpdate() { PixelValue pickValue, realValue; uint32_t x = (uint32_t)m_PickedPoint.x(); uint32_t y = (uint32_t)m_PickedPoint.y(); RT_FetchCurrentPixel(x, y, pickValue, realValue); m_Output->SetPixelContextLocation(x, y); m_CurHoverValue = pickValue; m_CurPixelValue = pickValue; m_CurRealValue = realValue; GUIInvoke::call([this]() { UI_UpdateStatusText(); }); } void TextureViewer::RT_PickHoverAndUpdate() { PixelValue pickValue, realValue; uint32_t x = (uint32_t)m_CurHoverPixel.x(); uint32_t y = (uint32_t)m_CurHoverPixel.y(); RT_FetchCurrentPixel(x, y, pickValue, realValue); m_CurHoverValue = pickValue; GUIInvoke::call([this]() { UI_UpdateStatusText(); }); } void TextureViewer::RT_UpdateAndDisplay() { if(m_Output != NULL) m_Output->SetTextureDisplay(m_TexDisplay); GUIInvoke::call([this]() { ui->render->update(); }); } void TextureViewer::UI_UpdateStatusText() { FetchTexture *texptr = m_Core->GetTexture(m_TexDisplay.texid); if(texptr == NULL) return; FetchTexture &tex = *texptr; bool dsv = ((tex.creationFlags & eTextureCreate_DSV) != 0) || (tex.format.compType == eCompType_Depth); bool uintTex = (tex.format.compType == eCompType_UInt); bool sintTex = (tex.format.compType == eCompType_SInt); if(m_TexDisplay.overlay == eTexOverlay_QuadOverdrawPass || m_TexDisplay.overlay == eTexOverlay_QuadOverdrawDraw) { dsv = false; uintTex = false; sintTex = true; } QColor swatchColor; if(dsv || uintTex || sintTex) { swatchColor = QColor(0, 0, 0); } else { float r = qBound(0.0f, m_CurHoverValue.value_f[0], 1.0f); float g = qBound(0.0f, m_CurHoverValue.value_f[1], 1.0f); float b = qBound(0.0f, m_CurHoverValue.value_f[2], 1.0f); if(tex.format.srgbCorrected || (tex.creationFlags & eTextureCreate_SwapBuffer) > 0) { r = powf(r, 1.0f / 2.2f); g = powf(g, 1.0f / 2.2f); b = powf(b, 1.0f / 2.2f); } swatchColor = QColor(int(255.0f * r), int(255.0f * g), int(255.0f * b)); } { QPalette Pal(palette()); Pal.setColor(QPalette::Background, swatchColor); ui->pickSwatch->setAutoFillBackground(true); ui->pickSwatch->setPalette(Pal); } int y = m_CurHoverPixel.y() >> (int)m_TexDisplay.mip; uint mipWidth = qMax(1U, tex.width >> (int)m_TexDisplay.mip); uint mipHeight = qMax(1U, tex.height >> (int)m_TexDisplay.mip); if(m_Core->APIProps().pipelineType == eGraphicsAPI_OpenGL) y = (int)(mipHeight - 1) - y; if(m_TexDisplay.FlipY) y = (int)(mipHeight - 1) - y; y = qMax(0, y); int x = m_CurHoverPixel.x() >> (int)m_TexDisplay.mip; float invWidth = mipWidth > 0 ? 1.0f / mipWidth : 0.0f; float invHeight = mipHeight > 0 ? 1.0f / mipHeight : 0.0f; QString hoverCoords = QString("%1, %2 (%3, %4)") .arg(x, 4) .arg(y, 4) .arg((x * invWidth), 5, 'f', 4) .arg((y * invHeight), 5, 'f', 4); QString statusText = "Hover - " + hoverCoords; uint32_t hoverX = (uint32_t)m_CurHoverPixel.x(); uint32_t hoverY = (uint32_t)m_CurHoverPixel.y(); if(hoverX > tex.width || hoverY > tex.height || hoverX < 0 || hoverY < 0) statusText = "Hover - [" + hoverCoords + "]"; if(m_PickedPoint.x() >= 0) { x = m_PickedPoint.x() >> (int)m_TexDisplay.mip; y = m_PickedPoint.y() >> (int)m_TexDisplay.mip; if(m_Core->APIProps().pipelineType == eGraphicsAPI_OpenGL) y = (int)(mipHeight - 1) - y; if(m_TexDisplay.FlipY) y = (int)(mipHeight - 1) - y; y = qMax(0, y); statusText += " - Right click - " + QString("%1, %2: ").arg(x, 4).arg(y, 4); PixelValue val = m_CurPixelValue; if(m_TexDisplay.CustomShader != ResourceId()) { statusText += Formatter::Format(val.value_f[0]) + ", " + Formatter::Format(val.value_f[1]) + ", " + Formatter::Format(val.value_f[2]) + ", " + Formatter::Format(val.value_f[3]); val = m_CurRealValue; statusText += " (Real: "; } if(dsv) { statusText += "Depth "; if(uintTex) { if(tex.format.compByteWidth == 2) statusText += Formatter::Format(val.value_u16[0]); else statusText += Formatter::Format(val.value_u[0]); } else { statusText += Formatter::Format(val.value_f[0]); } int stencil = (int)(255.0f * val.value_f[1]); statusText += QString(", Stencil %1 / 0x%2").arg(stencil).arg(stencil, 0, 16); } else { if(uintTex) { statusText += Formatter::Format(val.value_u[0]) + ", " + Formatter::Format(val.value_u[1]) + ", " + Formatter::Format(val.value_u[2]) + ", " + Formatter::Format(val.value_u[3]); } else if(sintTex) { statusText += Formatter::Format(val.value_i[0]) + ", " + Formatter::Format(val.value_i[1]) + ", " + Formatter::Format(val.value_i[2]) + ", " + Formatter::Format(val.value_i[3]); } else { statusText += Formatter::Format(val.value_f[0]) + ", " + Formatter::Format(val.value_f[1]) + ", " + Formatter::Format(val.value_f[2]) + ", " + Formatter::Format(val.value_f[3]); } } if(m_TexDisplay.CustomShader != ResourceId()) statusText += ")"; // PixelPicked = true; } else { statusText += " - Right click to pick a pixel"; m_Core->Renderer()->AsyncInvoke([this](IReplayRenderer *) { if(m_Output != NULL) m_Output->DisablePixelContext(); }); // PixelPicked = false; } // try and keep status text consistent by sticking to the high water mark // of length (prevents nasty oscillation when the length of the string is // just popping over/under enough to overflow onto the next line). if(statusText.length() > m_HighWaterStatusLength) m_HighWaterStatusLength = statusText.length(); if(statusText.length() < m_HighWaterStatusLength) statusText += QString(m_HighWaterStatusLength - statusText.length(), ' '); ui->statusText->setText(statusText); } void TextureViewer::UI_UpdateTextureDetails() { QString status; FetchTexture *texptr = m_Core->GetTexture(m_TexDisplay.texid); if(texptr == NULL) { ui->texStatusDim->setText(status); return; } FetchTexture ¤t = *texptr; #if 0 ResourceId followID = m_Following.GetResourceId(m_Core); { bool found = false; string name = ""; foreach(var t in m_Core.CurTextures) { if (t.ID == followID) { name = t.name; found = true; } } foreach(var b in m_Core.CurBuffers) { if (b.ID == followID) { name = b.name; found = true; } } if (followID == ResourceId.Null) { m_PreviewPanel.Text = "Unbound"; } else if (found) { switch (m_Following.Type) { case FollowType.OutputColour: m_PreviewPanel.Text = string.Format("Cur Output {0} - {1}", m_Following.index, name); break; case FollowType.OutputDepth: m_PreviewPanel.Text = string.Format("Cur Depth Output - {0}", name); break; case FollowType.ReadWrite: m_PreviewPanel.Text = string.Format("Cur RW Output - {0}", name); break; case FollowType.ReadOnly: m_PreviewPanel.Text = string.Format("Cur Input {0} - {1}", m_Following.index, name); break; } } else { switch (m_Following.Type) { case FollowType.OutputColour: m_PreviewPanel.Text = string.Format("Cur Output {0}", m_Following.index); break; case FollowType.OutputDepth: m_PreviewPanel.Text = string.Format("Cur Depth Output"); break; case FollowType.ReadWrite: m_PreviewPanel.Text = string.Format("Cur RW Output"); break; case FollowType.ReadOnly: m_PreviewPanel.Text = string.Format("Cur Input {0}", m_Following.index); break; } } } #endif status = QString(current.name.elems) + " - "; if(current.dimension >= 1) status += QString::number(current.width); if(current.dimension >= 2) status += "x" + QString::number(current.height); if(current.dimension >= 3) status += "x" + QString::number(current.depth); if(current.arraysize > 1) status += "[" + QString::number(current.arraysize) + "]"; if(current.msQual > 0 || current.msSamp > 1) status += QString(" MS{%1x %2Q}").arg(current.msSamp).arg(current.msQual); status += QString(" %1 mips").arg(current.mips); status += " - " + QString(current.format.strname.elems); if(current.format.compType != m_TexDisplay.typeHint && m_TexDisplay.typeHint != eCompType_None) { status += " Viewed as TODO"; // m_TexDisplay.typeHint.Str(); } ui->texStatusDim->setText(status); } void TextureViewer::UI_OnTextureSelectionChanged(bool newdraw) { UI_UpdateFittedScale(); UI_UpdateTextureDetails(); } void TextureViewer::render_mouseWheel(QWheelEvent *e) { QPoint cursorPos = e->pos(); setFitToWindow(false); // scroll in logarithmic scale double logScale = logf(m_TexDisplay.scale); logScale += e->delta() / 2500.0; UI_SetScale((float)expf(logScale), cursorPos.x(), cursorPos.y()); e->accept(); } void TextureViewer::render_mouseMove(QMouseEvent *e) { m_CurHoverPixel.setX(int(((float)e->x() - m_TexDisplay.offx) / m_TexDisplay.scale)); m_CurHoverPixel.setY(int(((float)e->y() - m_TexDisplay.offy) / m_TexDisplay.scale)); if(m_TexDisplay.texid != ResourceId()) { FetchTexture *texptr = m_Core->GetTexture(m_TexDisplay.texid); if(texptr != NULL) { if(e->buttons() & Qt::RightButton) { ui->render->setCursor(QCursor(Qt::CrossCursor)); m_PickedPoint = m_CurHoverPixel; m_PickedPoint.setX(qBound(0, m_PickedPoint.x(), (int)texptr->width - 1)); m_PickedPoint.setY(qBound(0, m_PickedPoint.y(), (int)texptr->height - 1)); m_Core->Renderer()->AsyncInvoke([this](IReplayRenderer *) { if(m_Output != NULL) RT_PickPixelsAndUpdate(); }); } else if(e->buttons() == Qt::NoButton) { m_Core->Renderer()->AsyncInvoke([this](IReplayRenderer *) { if(m_Output != NULL) RT_PickHoverAndUpdate(); }); } } } QPoint curpos = QCursor::pos(); if(e->buttons() & Qt::LeftButton) { if(qAbs(m_DragStartPos.x() - curpos.x()) > ui->renderHScroll->singleStep() || qAbs(m_DragStartPos.y() - curpos.y()) > ui->renderVScroll->singleStep()) { setScrollPosition(QPoint(m_DragStartScroll.x() + (curpos.x() - m_DragStartPos.x()), m_DragStartScroll.y() + (curpos.y() - m_DragStartPos.y()))); } ui->render->setCursor(QCursor(Qt::SizeAllCursor)); } if(e->buttons() == Qt::NoButton) { ui->render->setCursor(QCursor(Qt::ArrowCursor)); } UI_UpdateStatusText(); } void TextureViewer::render_mouseClick(QMouseEvent *e) { ui->render->setFocus(); if(e->buttons() & Qt::RightButton) render_mouseMove(e); if(e->buttons() & Qt::LeftButton) { m_DragStartPos = QCursor::pos(); m_DragStartScroll = getScrollPosition(); ui->render->setCursor(QCursor(Qt::SizeAllCursor)); } } void TextureViewer::render_resize(QResizeEvent *e) { UI_UpdateFittedScale(); UI_CalcScrollbars(); m_Core->Renderer()->AsyncInvoke([this](IReplayRenderer *) { RT_UpdateAndDisplay(); }); } float TextureViewer::CurMaxScrollX() { FetchTexture *texptr = m_Core->GetTexture(m_TexDisplay.texid); QSizeF size(1.0f, 1.0f); if(texptr != NULL) size = QSizeF(texptr->width, texptr->height); return ui->render->width() - size.width() * m_TexDisplay.scale; } float TextureViewer::CurMaxScrollY() { FetchTexture *texptr = m_Core->GetTexture(m_TexDisplay.texid); QSizeF size(1.0f, 1.0f); if(texptr != NULL) size = QSizeF(texptr->width, texptr->height); return ui->render->height() - size.height() * m_TexDisplay.scale; } QPoint TextureViewer::getScrollPosition() { return QPoint((int)m_TexDisplay.offx, m_TexDisplay.offy); } void TextureViewer::setScrollPosition(const QPoint &pos) { m_TexDisplay.offx = qMax(CurMaxScrollX(), (float)pos.x()); m_TexDisplay.offy = qMax(CurMaxScrollY(), (float)pos.y()); m_TexDisplay.offx = qMin(0.0f, m_TexDisplay.offx); m_TexDisplay.offy = qMin(0.0f, m_TexDisplay.offy); if(ScrollUpdateScrollbars) { if(ui->renderHScroll->isEnabled()) ui->renderHScroll->setValue(qBound(0, int(m_TexDisplay.offx), ui->renderHScroll->maximum())); if(ui->renderVScroll->isEnabled()) ui->renderVScroll->setValue(qBound(0, int(m_TexDisplay.offy), ui->renderVScroll->maximum())); } m_Core->Renderer()->AsyncInvoke([this](IReplayRenderer *) { RT_UpdateAndDisplay(); }); } void TextureViewer::UI_CalcScrollbars() { FetchTexture *texptr = m_Core->GetTexture(m_TexDisplay.texid); QSizeF size(1.0f, 1.0f); if(texptr != NULL) { size = QSizeF(texptr->width, texptr->height); } if((int)floor(size.width() * m_TexDisplay.scale) <= ui->render->width()) { ui->renderHScroll->setEnabled(false); } else { ui->renderHScroll->setEnabled(true); ui->renderHScroll->setMaximum( (int)ceil(size.width() * m_TexDisplay.scale - (float)ui->render->width())); ui->renderHScroll->setPageStep(qMax(1, ui->renderHScroll->maximum() / 6)); } if((int)floor(size.height() * m_TexDisplay.scale) <= ui->render->height()) { ui->renderVScroll->setEnabled(false); } else { ui->renderVScroll->setEnabled(true); ui->renderVScroll->setMaximum( (int)ceil(size.height() * m_TexDisplay.scale - (float)ui->render->height())); ui->renderVScroll->setPageStep(qMax(1, ui->renderVScroll->maximum() / 6)); } } void TextureViewer::on_renderHScroll_valueChanged(int position) { ScrollUpdateScrollbars = false; { float delta = (float)position / (float)ui->renderHScroll->maximum(); setScrollPosition(QPoint((int)(CurMaxScrollX() * delta), getScrollPosition().y())); } ScrollUpdateScrollbars = true; } void TextureViewer::on_renderVScroll_valueChanged(int position) { ScrollUpdateScrollbars = false; { float delta = (float)position / (float)ui->renderVScroll->maximum(); setScrollPosition(QPoint(getScrollPosition().x(), (int)(CurMaxScrollY() * delta))); } ScrollUpdateScrollbars = true; } void TextureViewer::OnLogfileLoaded() { #if defined(WIN32) WindowingSystem system = eWindowingSystem_Win32; HWND wnd = (HWND)ui->render->winId(); #elif defined(__linux__) XCBWindowData xcb = { QX11Info::connection(), (xcb_window_t)ui->render->winId(), }; XlibWindowData xlib = {QX11Info::display(), (Drawable)ui->render->winId()}; rdctype::array systems; m_Core->Renderer()->BlockInvoke( [&systems](IReplayRenderer *r) { r->GetSupportedWindowSystems(&systems); }); WindowingSystem system = eWindowingSystem_Unknown; void *wnd = NULL; // prefer XCB for(int32_t i = 0; i < systems.count; i++) { if(systems[i] == eWindowingSystem_XCB) { system = eWindowingSystem_XCB; wnd = &xcb; break; } } for(int32_t i = 0; wnd == NULL && i < systems.count; i++) { if(systems[i] == eWindowingSystem_Xlib) { system = eWindowingSystem_Xlib; wnd = &xlib; break; } } #else #error "Unknown platform" #endif m_Core->Renderer()->BlockInvoke([system, wnd, this](IReplayRenderer *r) { m_Output = r->CreateOutput(system, wnd, eOutputType_TexDisplay); ui->render->SetOutput(m_Core, m_Output); OutputConfig c = {eOutputType_TexDisplay}; m_Output->SetOutputConfig(c); }); } void TextureViewer::OnLogfileClosed() { m_Output = NULL; ui->render->SetOutput(m_Core, NULL); UI_UpdateTextureDetails(); } void TextureViewer::OnEventSelected(uint32_t eventID) { TextureDisplay &d = m_TexDisplay; if(m_Core->APIProps().pipelineType == eGraphicsAPI_D3D11) { d.texid = m_Core->CurD3D11PipelineState.m_OM.RenderTargets[0].Resource; } else if(m_Core->APIProps().pipelineType == eGraphicsAPI_OpenGL) { d.texid = m_Core->CurGLPipelineState.m_FB.m_DrawFBO.Color[0].Obj; } else { const VulkanPipelineState &pipe = m_Core->CurVulkanPipelineState; if(pipe.Pass.renderpass.colorAttachments.count > 0) d.texid = pipe.Pass.framebuffer.attachments[pipe.Pass.renderpass.colorAttachments[0]].img; if(d.texid == ResourceId()) { const FetchDrawcall *draw = m_Core->CurDrawcall(); if(draw) d.texid = draw->copyDestination; } } d.mip = 0; d.sampleIdx = ~0U; d.overlay = eTexOverlay_None; d.CustomShader = ResourceId(); d.HDRMul = -1.0f; d.linearDisplayAsGamma = true; d.FlipY = false; d.rangemin = 0.0f; d.rangemax = 1.0f; d.scale = 1.0f; d.offx = 0.0f; d.offy = 0.0f; d.sliceFace = 0; d.rawoutput = false; d.lightBackgroundColour = d.darkBackgroundColour = FloatVector(0.0f, 0.0f, 0.0f, 0.0f); d.Red = d.Green = d.Blue = true; d.Alpha = false; m_Core->Renderer()->AsyncInvoke([this](IReplayRenderer *) { RT_UpdateAndDisplay(); }); FetchTexture *tex = m_Core->GetTexture(d.texid); if(tex) ui->renderContainer->setWindowTitle(tr(tex->name.elems)); // if (!CurrentTextureIsLocked || (CurrentTexture != null && m_TexDisplay.texid != // CurrentTexture.ID)) UI_OnTextureSelectionChanged(true); } float TextureViewer::GetFitScale() { FetchTexture *texptr = m_Core->GetTexture(m_TexDisplay.texid); if(texptr == NULL) return 1.0f; float xscale = (float)ui->render->width() / (float)texptr->width; float yscale = (float)ui->render->height() / (float)texptr->height; return qMin(xscale, yscale); } void TextureViewer::UI_UpdateFittedScale() { if(ui->fitToWindow->isChecked()) UI_SetScale(1.0f); } void TextureViewer::UI_SetScale(float s) { UI_SetScale(s, ui->render->width() / 2, ui->render->height() / 2); } void TextureViewer::UI_SetScale(float s, int x, int y) { if(ui->fitToWindow->isChecked()) s = GetFitScale(); float prevScale = m_TexDisplay.scale; m_TexDisplay.scale = qBound(0.1f, s, 256.0f); m_Core->Renderer()->AsyncInvoke([this](IReplayRenderer *) { RT_UpdateAndDisplay(); }); float scaleDelta = (m_TexDisplay.scale / prevScale); QPoint newPos = getScrollPosition(); newPos -= QPoint(x, y); newPos = QPoint((int)(newPos.x() * scaleDelta), (int)(newPos.y() * scaleDelta)); newPos += QPoint(x, y); setScrollPosition(newPos); setCurrentZoomValue(m_TexDisplay.scale); UI_CalcScrollbars(); } void TextureViewer::setCurrentZoomValue(float zoom) { ui->zoomOption->setCurrentText(QString::number(ceil(zoom * 100)) + "%"); } float TextureViewer::getCurrentZoomValue() { if(ui->fitToWindow->isChecked()) return m_TexDisplay.scale; QString zoomText = ui->zoomOption->currentText().replace('%', ' '); bool ok = false; int zoom = zoomText.toInt(&ok); if(!ok) zoom = 100; return (float)(zoom) / 100.0f; } void TextureViewer::setFitToWindow(bool checked) { if(checked) { UI_UpdateFittedScale(); ui->fitToWindow->setChecked(true); } else if(!checked) { ui->fitToWindow->setChecked(false); float curScale = m_TexDisplay.scale; ui->zoomOption->setCurrentText(""); setCurrentZoomValue(curScale); } } void TextureViewer::on_fitToWindow_toggled(bool checked) { UI_UpdateFittedScale(); } void TextureViewer::on_zoomExactSize_clicked() { ui->fitToWindow->setChecked(false); UI_SetScale(1.0f); } void TextureViewer::on_zoomOption_currentIndexChanged(int index) { if(index >= 0) { setFitToWindow(false); ui->zoomOption->setCurrentText(ui->zoomOption->itemText(index)); UI_SetScale(getCurrentZoomValue()); } } void TextureViewer::on_zoomOption_returnPressed() { UI_SetScale(getCurrentZoomValue()); }