From fbcb04944dc71be013a0f86e3ae850c2d1c5db22 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 12 Oct 2016 00:26:15 +0200 Subject: [PATCH] Add texture saving dialog --- qrenderdoc/Code/CaptureContext.cpp | 4 +- qrenderdoc/Code/CaptureContext.h | 11 +- .../Windows/Dialogs/TextureSaveDialog.cpp | 609 ++++++++++++++++++ .../Windows/Dialogs/TextureSaveDialog.h | 83 +++ .../Windows/Dialogs/TextureSaveDialog.ui | 385 +++++++++++ qrenderdoc/Windows/TextureViewer.cpp | 72 ++- qrenderdoc/qrenderdoc.pro | 9 +- qrenderdoc/qrenderdoc_local.vcxproj | 5 + qrenderdoc/qrenderdoc_local.vcxproj.filters | 18 + renderdoc/api/replay/renderdoc_replay.h | 2 + 10 files changed, 1185 insertions(+), 13 deletions(-) create mode 100644 qrenderdoc/Windows/Dialogs/TextureSaveDialog.cpp create mode 100644 qrenderdoc/Windows/Dialogs/TextureSaveDialog.h create mode 100644 qrenderdoc/Windows/Dialogs/TextureSaveDialog.ui diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 99bb322d2..7a403ea13 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -295,7 +295,7 @@ void RDDialog::show(QMenu *menu, QPoint pos) } } -void RDDialog::show(QDialog *dialog) +int RDDialog::show(QDialog *dialog) { dialog->setWindowModality(Qt::ApplicationModal); dialog->show(); @@ -305,6 +305,8 @@ void RDDialog::show(QDialog *dialog) loop.processEvents(QEventLoop::WaitForMoreEvents); QCoreApplication::sendPostedEvents(); } + + return dialog->result(); } QMessageBox::StandardButton RDDialog::messageBox(QMessageBox::Icon icon, QWidget *parent, diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 1e5d0684e..b519e74b3 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -54,6 +54,15 @@ struct ILogLoadProgressListener class MainWindow; +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); } +}; + class CaptureContext { public: @@ -258,7 +267,7 @@ class QMenu; struct RDDialog { static void show(QMenu *menu, QPoint pos); - static void show(QDialog *dialog); + static int show(QDialog *dialog); static QMessageBox::StandardButton messageBox( QMessageBox::Icon, QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, diff --git a/qrenderdoc/Windows/Dialogs/TextureSaveDialog.cpp b/qrenderdoc/Windows/Dialogs/TextureSaveDialog.cpp new file mode 100644 index 000000000..4d951bf08 --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/TextureSaveDialog.cpp @@ -0,0 +1,609 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "TextureSaveDialog.h" +#include +#include "Code/CaptureContext.h" +#include "ui_TextureSaveDialog.h" + +TextureSaveDialog::TextureSaveDialog(const FetchTexture &t, const TextureSave &s, QWidget *parent) + : QDialog(parent), ui(new Ui::TextureSaveDialog) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + ui->setupUi(this); + + ui->fileFormat->clear(); + + // TODO + + ui->fileFormat->addItems({"DDS", "PNG", "JPG", "BMP", "TGA", "HDR", "EXR"}); + ui->alphaMap->addItems({"Discard", "Blend to Colour", "Blend to Checkerboard"}); + + tex = t; + saveData = s; + + ui->jpegCompression->setValue(saveData.jpegQuality); + + ui->alphaMap->setCurrentIndex((int)saveData.alpha); + + ui->blackPoint->setText(Formatter::Format(saveData.comp.blackPoint)); + ui->whitePoint->setText(Formatter::Format(saveData.comp.whitePoint)); + + for(uint32_t i = 0; i < tex.mips; i++) + ui->mipSelect->addItem(QString::number(i) + " - " + QString::number(qMax(1U, tex.width >> i)) + + "x" + QString::number(qMax(1U, tex.height >> i))); + + ui->mipSelect->setCurrentIndex(saveData.mip >= 0 ? saveData.mip : 0); + + for(uint32_t i = 0; i < tex.msSamp; i++) + ui->sampleSelect->addItem(QString("Sample %1").arg(i)); + + ui->sampleSelect->setCurrentIndex(qMin( + (int)tex.msSamp, (saveData.sample.sampleIndex == ~0U ? 0 : (int)saveData.sample.sampleIndex))); + + if(saveData.sample.sampleIndex == ~0U) + { + ui->resolveSamples->setChecked(true); + } + else + { + ui->oneSample->setChecked(true); + } + + const char *cubeFaces[] = {"X+", "X-", "Y+", "Y-", "Z+", "Z-"}; + + uint32_t numSlices = (qMax(1U, tex.depth) * tex.numSubresources) / tex.mips; + + for(uint32_t i = 0; i < numSlices; i++) + { + if(tex.cubemap) + { + QString name = cubeFaces[i % 6]; + if(numSlices > 6) + name = QString("[%1] %2").arg(i / 6).arg( + cubeFaces[i % 6]); // Front 1, Back 2, 3, 4 etc for cube arrays + ui->sliceSelect->addItem(name); + } + else + { + ui->sliceSelect->addItem(QString("Slice %1").arg(i)); + } + } + + ui->sliceSelect->setCurrentIndex(saveData.slice.sliceIndex >= 0 ? saveData.slice.sliceIndex : 0); + + ui->gridWidth->setMaximum(tex.depth * tex.arraysize * tex.msSamp); + + ui->mipGroup->setVisible(tex.mips > 1); + + ui->sampleGroup->setVisible(tex.msSamp > 1); + + ui->sliceGroup->setVisible(tex.depth > 1 || tex.arraysize > 1 || tex.msSamp > 1); + + if(saveData.destType != eFileType_DDS) + { + ui->cubeCruciform->setEnabled(tex.cubemap && tex.arraysize == 6); + + if(!ui->oneSlice->isChecked() && !ui->cubeCruciform->isEnabled()) + ui->mapSlicesToGrid->setChecked(true); + } + + ui->fileFormat->setCurrentIndex((int)saveData.destType); + + adjustSize(); +} + +TextureSaveDialog::~TextureSaveDialog() +{ + delete ui; +} + +QString TextureSaveDialog::filename() +{ + return ui->filename->text(); +} + +void TextureSaveDialog::SetFiletypeFromFilename() +{ + QFileInfo path(ui->filename->text()); + QString ext = path.suffix().toUpper(); + + // TODO + + /* + foreach(var ft in(FileType[])Enum.GetValues(typeof(FileType))) + { + if(ft.ToString().ToUpperInvariant() == ext) + { + fileFormat.SelectedIndex = (int)ft; + break; + } + } + */ +} + +void TextureSaveDialog::SetFilenameFromFiletype() +{ + QFileInfo path(ui->filename->text()); + QString ext = path.suffix().toUpper(); + + // TODO + + /* + FileType[] types = (FileType[])Enum.GetValues(typeof(FileType)); + + string selectedExt = types[fileFormat.SelectedIndex].ToString().ToLowerInvariant(); + + if(selectedExt != filenameExt) + { + filename.Text = filename.Text.Substring(0, filename.Text.Length - filenameExt.Length); + filename.Text += selectedExt; + } + */ +} + +void TextureSaveDialog::on_fileFormat_currentIndexChanged(int index) +{ + saveData.destType = (FileType)ui->fileFormat->currentIndex(); + + ui->jpegCompression->setEnabled(saveData.destType == eFileType_JPG); + + ui->alphaGroup->setVisible(saveData.destType != eFileType_HDR && + saveData.destType != eFileType_EXR && + saveData.destType != eFileType_DDS); + + bool noAlphaFormat = (saveData.destType == eFileType_BMP || saveData.destType == eFileType_JPG); + + ui->alphaMap->setEnabled(tex.format.compCount == 4 && noAlphaFormat); + + ui->alphaCol->setEnabled(saveData.alpha == eAlphaMap_BlendToColour && tex.format.compCount == 4 && + noAlphaFormat); + + if(saveData.destType == eFileType_DDS) + { + ui->exportAllMips->setEnabled(true); + ui->exportAllMips->setChecked(true); + + ui->exportAllSlices->setEnabled(true); + ui->exportAllSlices->setChecked(true); + + ui->cubeCruciform->setEnabled(true); + ui->cubeCruciform->setChecked(false); + + ui->gridWidth->setEnabled(false); + + ui->mapSlicesToGrid->setEnabled(false); + ui->mapSlicesToGrid->setChecked(false); + } + else + { + ui->exportAllMips->setEnabled(false); + ui->oneMip->setChecked(true); + ui->oneSlice->setChecked(true); + } + SetFilenameFromFiletype(); + + adjustSize(); +} + +void TextureSaveDialog::on_jpegCompression_valueChanged(double arg1) +{ + saveData.jpegQuality = (int)arg1; +} + +void TextureSaveDialog::on_exportAllMips_toggled(bool) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + ui->oneMip->setChecked(!ui->exportAllMips->isChecked()); + ui->mipSelect->setEnabled(ui->oneMip->isChecked()); + + m_Recurse = false; +} + +void TextureSaveDialog::on_oneMip_toggled(bool checked) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + ui->exportAllMips->setChecked(!ui->oneMip->isChecked()); + ui->mipSelect->setEnabled(ui->oneMip->isChecked()); + + if(saveData.destType != eFileType_DDS) + { + ui->oneMip->setChecked(true); + ui->exportAllMips->setChecked(false); + ui->mipSelect->setEnabled(true); + } + + m_Recurse = false; +} + +void TextureSaveDialog::on_mipSelect_currentIndexChanged(int index) +{ + saveData.mip = index; +} + +void TextureSaveDialog::on_mapSampleArray_toggled(bool checked) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + if(ui->mapSampleArray->isChecked()) + { + ui->resolveSamples->setChecked(false); + ui->oneSample->setChecked(false); + } + else + { + ui->resolveSamples->setChecked(false); + ui->oneSample->setChecked(true); + } + ui->sampleSelect->setEnabled(ui->oneSample->isChecked()); + + m_Recurse = false; +} + +void TextureSaveDialog::on_resolveSamples_toggled(bool checked) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + if(ui->resolveSamples->isChecked()) + { + ui->mapSampleArray->setChecked(false); + ui->oneSample->setChecked(false); + } + else + { + ui->mapSampleArray->setChecked(false); + ui->oneSample->setChecked(true); + } + ui->sampleSelect->setEnabled(ui->oneSample->isChecked()); + + m_Recurse = false; +} + +void TextureSaveDialog::on_oneSample_toggled(bool checked) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + if(ui->oneSample->isChecked()) + { + ui->mapSampleArray->setChecked(false); + ui->resolveSamples->setChecked(false); + } + else + { + ui->mapSampleArray->setChecked(false); + ui->resolveSamples->setChecked(true); + } + ui->sampleSelect->setEnabled(ui->oneSample->isChecked()); + + m_Recurse = false; +} + +void TextureSaveDialog::on_sampleSelect_currentIndexChanged(int index) +{ + saveData.sample.sampleIndex = (uint32_t)index; +} + +void TextureSaveDialog::on_exportAllSlices_toggled(bool checked) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + ui->oneSlice->setChecked(!ui->exportAllSlices->isChecked()); + if(saveData.destType == eFileType_DDS) + { + ui->mapSlicesToGrid->setEnabled(false); + ui->gridWidth->setEnabled(false); + ui->cubeCruciform->setEnabled(false); + } + else + { + ui->mapSlicesToGrid->setEnabled(!ui->oneSlice->isChecked()); + ui->gridWidth->setEnabled(!ui->oneSlice->isChecked()); + + if(!ui->oneSlice->isChecked() && !ui->cubeCruciform->isChecked()) + ui->mapSlicesToGrid->setChecked(true); + + if(tex.cubemap && tex.arraysize == 6) + ui->cubeCruciform->setEnabled(!ui->oneSlice->isChecked()); + else + ui->cubeCruciform->setEnabled(false); + } + ui->sliceSelect->setEnabled(ui->oneSlice->isChecked()); + + m_Recurse = false; +} + +void TextureSaveDialog::on_oneSlice_toggled(bool checked) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + ui->exportAllSlices->setChecked(!ui->oneSlice->isChecked()); + if(saveData.destType == eFileType_DDS) + { + ui->mapSlicesToGrid->setEnabled(false); + ui->gridWidth->setEnabled(false); + ui->cubeCruciform->setEnabled(false); + } + else + { + ui->mapSlicesToGrid->setEnabled(!ui->oneSlice->isChecked()); + ui->gridWidth->setEnabled(!ui->oneSlice->isChecked()); + + if(!ui->oneSlice->isChecked() && !ui->cubeCruciform->isChecked()) + ui->mapSlicesToGrid->setChecked(true); + + if(tex.cubemap && tex.arraysize == 6) + ui->cubeCruciform->setEnabled(!ui->oneSlice->isChecked()); + else + ui->cubeCruciform->setEnabled(false); + } + ui->sliceSelect->setEnabled(ui->oneSlice->isChecked()); + + m_Recurse = false; +} + +void TextureSaveDialog::on_mapSlicesToGrid_toggled(bool checked) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + if(ui->mapSlicesToGrid->isChecked()) + { + ui->cubeCruciform->setChecked(false); + } + else if(saveData.destType != eFileType_DDS) + { + ui->oneSlice->setChecked(true); + ui->exportAllSlices->setChecked(false); + ui->cubeCruciform->setEnabled(false); + ui->mapSlicesToGrid->setEnabled(false); + ui->gridWidth->setEnabled(false); + ui->sliceSelect->setEnabled(true); + } + + m_Recurse = false; + + if(saveData.destType == eFileType_DDS) + ui->gridWidth->setEnabled(false); + else + ui->gridWidth->setEnabled(ui->mapSlicesToGrid->isChecked()); +} + +void TextureSaveDialog::on_cubeCruciform_toggled(bool checked) +{ + if(m_Recurse) + return; + + m_Recurse = true; + + if(ui->cubeCruciform->isChecked()) + { + ui->mapSlicesToGrid->setChecked(false); + } + else if(saveData.destType != eFileType_DDS) + { + ui->oneSlice->setChecked(true); + ui->exportAllSlices->setChecked(false); + ui->cubeCruciform->setEnabled(false); + ui->mapSlicesToGrid->setEnabled(false); + ui->gridWidth->setEnabled(false); + ui->sliceSelect->setEnabled(true); + } + + m_Recurse = false; +} + +void TextureSaveDialog::on_sliceSelect_currentIndexChanged(int index) +{ + saveData.slice.sliceIndex = index; +} + +void TextureSaveDialog::on_gridWidth_valueChanged(double arg1) +{ + saveData.slice.sliceGridWidth = (int)arg1; +} + +void TextureSaveDialog::on_alphaCol_clicked() +{ + QColor col = QColorDialog::getColor(Qt::black, this, tr("Choose background colour")); + + if(col.isValid()) + { + col = col.toRgb(); + + saveData.alphaCol = FloatVector(col.redF(), col.greenF(), col.blueF(), 1.0f); + } +} + +void TextureSaveDialog::on_alphaMap_currentIndexChanged(int index) +{ + saveData.alpha = (AlphaMapping)index; + + ui->alphaCol->setEnabled(saveData.alpha == eAlphaMap_BlendToColour); +} + +void TextureSaveDialog::on_blackPoint_textEdited(const QString &arg) +{ + bool ok = false; + double d = arg.toDouble(&ok); + + if(ok) + saveData.comp.blackPoint = d; +} + +void TextureSaveDialog::on_whitePoint_textEdited(const QString &arg) +{ + bool ok = false; + double d = arg.toDouble(&ok); + + if(ok) + saveData.comp.whitePoint = d; +} + +void TextureSaveDialog::on_browse_clicked() +{ + QString filter = "TODO"; + QString *selectedFilter = NULL; + + QString filename = + RDDialog::getSaveFileName(this, tr("Save Texture As"), "", filter, selectedFilter); + + QFileInfo checkFile(filename); + if(filename != "") + { + ui->filename->setText(filename); + SetFiletypeFromFilename(); + } +} + +void TextureSaveDialog::on_filename_textEdited(const QString &arg1) +{ +} + +void TextureSaveDialog::on_saveCancelButtons_accepted() +{ + saveData.alpha = (AlphaMapping)ui->alphaMap->currentIndex(); + + if(saveData.alpha == eAlphaMap_BlendToCheckerboard) + { + saveData.alphaCol = FloatVector(0.666f, 0.666f, 0.666f, 1.0f); + } + + if(ui->exportAllMips->isChecked()) + saveData.mip = -1; + else + saveData.mip = (int)ui->mipSelect->currentIndex(); + + if(ui->resolveSamples->isChecked()) + { + saveData.sample.sampleIndex = ~0U; + saveData.sample.mapToArray = false; + } + else if(ui->mapSampleArray->isChecked()) + { + saveData.sample.sampleIndex = 0; + saveData.sample.mapToArray = true; + } + else + { + saveData.sample.sampleIndex = (uint)ui->sampleSelect->currentIndex(); + saveData.sample.mapToArray = false; + } + + if(!ui->exportAllSlices->isChecked()) + { + saveData.slice.cubeCruciform = saveData.slice.slicesAsGrid = false; + saveData.slice.sliceGridWidth = 1; + saveData.slice.sliceIndex = (int)ui->sliceSelect->currentIndex(); + } + else + { + saveData.slice.sliceIndex = -1; + if(ui->cubeCruciform->isChecked()) + { + saveData.slice.cubeCruciform = true; + saveData.slice.slicesAsGrid = false; + saveData.slice.sliceGridWidth = 1; + } + else + { + saveData.slice.cubeCruciform = false; + saveData.slice.slicesAsGrid = true; + saveData.slice.sliceGridWidth = (int)ui->gridWidth->value(); + } + } + + saveData.destType = (FileType)ui->fileFormat->currentIndex(); + saveData.jpegQuality = (int)ui->jpegCompression->value(); + + bool ok = false; + double d = 0.0; + + d = ui->blackPoint->text().toDouble(&ok); + + if(ok) + saveData.comp.blackPoint = d; + + d = ui->whitePoint->text().toDouble(&ok); + + if(ok) + saveData.comp.whitePoint = d; + + QFileInfo fi(filename()); + + QDir dir = fi.dir(); + + bool valid = dir.makeAbsolute(); + + if(!valid || !dir.exists()) + { + RDDialog::critical(this, tr("Save Texture"), + tr("%1\nPath does not exist.\nCheck the path and try again.").arg(filename())); + + return; + } + + if(fi.exists()) + { + QMessageBox::StandardButton button = + RDDialog::question(this, tr("Confirm Save Texture"), + tr("%1 already exists.\nDo you want to replace it?").arg(fi.fileName())); + + if(button != QMessageBox::Yes) + return; + } + + // path is valid and either doesn't exist or user confirmed replacement + setResult(1); + accept(); +} + +void TextureSaveDialog::on_saveCancelButtons_rejected() +{ + reject(); +} diff --git a/qrenderdoc/Windows/Dialogs/TextureSaveDialog.h b/qrenderdoc/Windows/Dialogs/TextureSaveDialog.h new file mode 100644 index 000000000..bdbb05e79 --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/TextureSaveDialog.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include +#include "renderdoc_replay.h" + +namespace Ui +{ +class TextureSaveDialog; +} + +class TextureSaveDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TextureSaveDialog(const FetchTexture &t, const TextureSave &s, QWidget *parent = 0); + ~TextureSaveDialog(); + + QString filename(); + + TextureSave config() { return saveData; } +private slots: + void on_fileFormat_currentIndexChanged(int index); + void on_jpegCompression_valueChanged(double arg1); + void on_exportAllMips_toggled(bool checked); + void on_oneMip_toggled(bool checked); + void on_mipSelect_currentIndexChanged(int index); + void on_mapSampleArray_toggled(bool checked); + void on_resolveSamples_toggled(bool checked); + void on_oneSample_toggled(bool checked); + void on_sampleSelect_currentIndexChanged(int index); + void on_exportAllSlices_toggled(bool checked); + void on_oneSlice_toggled(bool checked); + void on_mapSlicesToGrid_toggled(bool checked); + void on_cubeCruciform_toggled(bool checked); + void on_sliceSelect_currentIndexChanged(int index); + void on_gridWidth_valueChanged(double arg1); + void on_alphaCol_clicked(); + void on_alphaMap_currentIndexChanged(int index); + void on_blackPoint_textEdited(const QString &arg1); + void on_whitePoint_textEdited(const QString &arg1); + void on_browse_clicked(); + void on_filename_textEdited(const QString &arg1); + + void on_saveCancelButtons_accepted(); + + void on_saveCancelButtons_rejected(); + +private: + Ui::TextureSaveDialog *ui; + + void SetFilenameFromFiletype(); + void SetFiletypeFromFilename(); + + FetchTexture tex; + TextureSave saveData; + + bool m_Recurse = false; +}; diff --git a/qrenderdoc/Windows/Dialogs/TextureSaveDialog.ui b/qrenderdoc/Windows/Dialogs/TextureSaveDialog.ui new file mode 100644 index 000000000..a2f9d51f7 --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/TextureSaveDialog.ui @@ -0,0 +1,385 @@ + + + TextureSaveDialog + + + + 0 + 0 + 400 + 527 + + + + + 0 + 0 + + + + + 400 + 0 + + + + Save Texture + + + true + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + Path + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + ... + + + + + + + + + + + 0 + 0 + + + + File Format + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + File Format: + + + + + + + + + + JPEG Compression: + + + + + + + 0 + + + 100.000000000000000 + + + + + + + + + + Mips + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Select Mip: + + + + + + + + + + Export All Mips + + + + + + + + + + MSAA Samples + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Map Samples as Array Slices + + + + + + + Resolve Samples + + + + + + + Select Sample: + + + + + + + + + + + + + Array/Depth Slices + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Export All Slices + + + + + + + Select Slice: + + + + + + + + + + Show Slices as Grid. Grid Width: + + + + + + + 0 + + + + + + + Show Cubemap as Cross + + + + + + + + + + Alpha Handling + + + + 3 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + 1 + 0 + + + + Alpha mapping: + + + + + + + + 2 + 0 + + + + + + + + + 1 + 0 + + + + Background Colour + + + + + + + + + + + Black Point: + + + + + + + + + + White Point: + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + false + + + + + + + + diff --git a/qrenderdoc/Windows/TextureViewer.cpp b/qrenderdoc/Windows/TextureViewer.cpp index 5b8b59118..a8b74aae2 100644 --- a/qrenderdoc/Windows/TextureViewer.cpp +++ b/qrenderdoc/Windows/TextureViewer.cpp @@ -31,19 +31,12 @@ #include #include "3rdparty/toolwindowmanager/ToolWindowManagerArea.h" #include "Code/CaptureContext.h" +#include "Dialogs/TextureSaveDialog.h" #include "Widgets/ResourcePreview.h" #include "Widgets/TextureGoto.h" #include "FlowLayout.h" #include "ui_TextureViewer.h" -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); } -}; float area(const QSizeF &s) { @@ -3118,6 +3111,69 @@ void TextureViewer::on_viewTexBuffer_clicked() void TextureViewer::on_saveTex_clicked() { + FetchTexture *texptr = GetCurrentTexture(); + + if(!texptr || !m_Output) + return; + + TextureSave config = {}; + + config.id = m_TexDisplay.texid; + config.typeHint = m_TexDisplay.typeHint; + config.slice.sliceIndex = (int)m_TexDisplay.sliceFace; + config.mip = (int)m_TexDisplay.mip; + + if(texptr->depth > 1) + config.slice.sliceIndex = (int)m_TexDisplay.sliceFace >> (int)m_TexDisplay.mip; + + config.channelExtract = -1; + if(m_TexDisplay.Red && !m_TexDisplay.Green && !m_TexDisplay.Blue && !m_TexDisplay.Alpha) + config.channelExtract = 0; + if(!m_TexDisplay.Red && m_TexDisplay.Green && !m_TexDisplay.Blue && !m_TexDisplay.Alpha) + config.channelExtract = 1; + if(!m_TexDisplay.Red && !m_TexDisplay.Green && m_TexDisplay.Blue && !m_TexDisplay.Alpha) + config.channelExtract = 2; + if(!m_TexDisplay.Red && !m_TexDisplay.Green && !m_TexDisplay.Blue && m_TexDisplay.Alpha) + config.channelExtract = 3; + + config.comp.blackPoint = m_TexDisplay.rangemin; + config.comp.whitePoint = m_TexDisplay.rangemax; + config.alphaCol = m_TexDisplay.lightBackgroundColour; + config.alpha = m_TexDisplay.Alpha ? eAlphaMap_BlendToCheckerboard : eAlphaMap_Discard; + if(m_TexDisplay.Alpha && !ui->checkerBack->isChecked()) + config.alpha = eAlphaMap_BlendToColour; + + if(m_TexDisplay.CustomShader != ResourceId()) + { + ResourceId id; + m_Ctx->Renderer()->BlockInvoke( + [this, &id](IReplayRenderer *r) { id = m_Output->GetCustomShaderTexID(); }); + + if(id != ResourceId()) + config.id = id; + } + + TextureSaveDialog saveDialog(*texptr, config, this); + int res = RDDialog::show(&saveDialog); + + config = saveDialog.config(); + + if(res) + { + bool ret = false; + QString fn = saveDialog.filename(); + + m_Ctx->Renderer()->BlockInvoke([this, &ret, config, fn](IReplayRenderer *r) { + ret = r->SaveTexture(config, fn.toUtf8().data()); + }); + + if(!ret) + { + RDDialog::critical( + NULL, tr("Error saving texture"), + tr("Error saving texture %1.\n\nCheck diagnostic log in Help menu for more details.").arg(fn)); + } + } } void TextureViewer::on_texListShow_clicked() diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 70a915272..f7f70809c 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -100,7 +100,8 @@ SOURCES += 3rdparty/toolwindowmanager/ToolWindowManager.cpp \ Widgets/ResourcePreview.cpp \ Widgets/ThumbnailStrip.cpp \ Widgets/TextureGoto.cpp \ - Widgets/RangeHistogram.cpp + Widgets/RangeHistogram.cpp \ + Windows/Dialogs/TextureSaveDialog.cpp HEADERS += 3rdparty/toolwindowmanager/ToolWindowManager.h \ 3rdparty/toolwindowmanager/ToolWindowManagerArea.h \ @@ -122,14 +123,16 @@ HEADERS += 3rdparty/toolwindowmanager/ToolWindowManager.h \ Widgets/ResourcePreview.h \ Widgets/ThumbnailStrip.h \ Widgets/TextureGoto.h \ - Widgets/RangeHistogram.h + Widgets/RangeHistogram.h \ + Windows/Dialogs/TextureSaveDialog.h FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/MainWindow.ui \ Windows/EventBrowser.ui \ Windows/TextureViewer.ui \ Widgets/ResourcePreview.ui \ - Widgets/ThumbnailStrip.ui + Widgets/ThumbnailStrip.ui \ + Windows/Dialogs/TextureSaveDialog.ui RESOURCES += \ resources.qrc diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 7ab05e9a6..7fad21e1c 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -290,6 +290,7 @@ + @@ -306,6 +307,7 @@ + Level3 @@ -332,6 +334,7 @@ + @@ -344,6 +347,7 @@ + @@ -358,6 +362,7 @@ + diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index bfadf41af..eab69ddde 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -163,6 +163,12 @@ Windows\Dialogs + + Windows\Dialogs + + + Generated Files + @@ -234,6 +240,12 @@ Windows\Dialogs + + Windows\Dialogs + + + Generated Files + @@ -432,6 +444,12 @@ Widgets + + Windows\Dialogs + + + Windows\Dialogs + diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 4df7c2269..a7e79a923 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -166,8 +166,10 @@ struct IReplayOutput virtual bool GetHistogram(float minval, float maxval, bool channels[4], rdctype::array *histogram) = 0; + virtual ResourceId GetCustomShaderTexID() = 0; virtual bool PickPixel(ResourceId texID, bool customShader, uint32_t x, uint32_t y, uint32_t sliceFace, uint32_t mip, uint32_t sample, PixelValue *val) = 0; + virtual uint32_t PickVertex(uint32_t eventID, uint32_t x, uint32_t y) = 0; }; #endif