From 7f4e473e8cf8f92cf69fac90eeda7e6defcc1eb2 Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Wed, 14 Oct 2015 17:45:21 +0200 Subject: [PATCH] Add new shader viewing tool --- CMakeLists.txt | 1 + Core/Config.cpp | 1 + Core/Config.h | 2 +- GPU/Common/ShaderCommon.h | 29 ++++ GPU/GLES/FragmentShaderGenerator.cpp | 33 ++++ GPU/GLES/FragmentShaderGenerator.h | 1 + GPU/GLES/GLES_GPU.cpp | 7 + GPU/GLES/GLES_GPU.h | 4 + GPU/GLES/ShaderManager.cpp | 80 ++++++++- GPU/GLES/ShaderManager.h | 27 ++- GPU/GLES/VertexShaderGenerator.cpp | 82 ++++++--- GPU/GLES/VertexShaderGenerator.h | 6 +- GPU/GPU.vcxproj | 1 + GPU/GPU.vcxproj.filters | 5 +- GPU/GPUCommon.h | 5 + GPU/GPUInterface.h | 5 + UI/DevScreens.cpp | 247 +++++++++------------------ UI/DevScreens.h | 25 +++ UI/EmuScreen.cpp | 1 + UI/ProfilerDraw.cpp | 192 +++++++++++++++++++++ UI/ProfilerDraw.h | 9 + UI/UI.vcxproj | 4 +- UI/UI.vcxproj.filters | 6 + Windows/MainWindow.cpp | 2 +- android/jni/Android.mk | 1 + 25 files changed, 565 insertions(+), 211 deletions(-) create mode 100644 GPU/Common/ShaderCommon.h create mode 100644 UI/ProfilerDraw.cpp create mode 100644 UI/ProfilerDraw.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cb370df423..fda6b8ffe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -863,6 +863,7 @@ set(NativeAppSource UI/Store.cpp UI/CwCheatScreen.cpp UI/InstallZipScreen.cpp + UI/ProfilerDraw.cpp UI/ui_atlas.cpp) if(ANDROID) if (ARM) diff --git a/Core/Config.cpp b/Core/Config.cpp index 153ad839f6..859e0cf556 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -323,6 +323,7 @@ static ConfigSetting generalSettings[] = { #endif ConfigSetting("PauseWhenMinimized", &g_Config.bPauseWhenMinimized, false, true, true), ConfigSetting("DumpDecryptedEboots", &g_Config.bDumpDecryptedEboot, false, true, true), + ConfigSetting("FullscreenOnDoubleclick", &g_Config.bFullscreenOnDoubleclick, true, false, false), ConfigSetting(false), }; diff --git a/Core/Config.h b/Core/Config.h index a6a5532bb4..09d230a8c7 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -90,12 +90,12 @@ public: bool bScreenshotsAsPNG; bool bEnableLogging; bool bDumpDecryptedEboot; + bool bFullscreenOnDoubleclick; #if defined(USING_WIN_UI) bool bPauseOnLostFocus; bool bTopMost; std::string sFont; bool bIgnoreWindowsKey; - // Used for switching the GPU backend in GameSettingsScreen. // Without this, PPSSPP instantly crashes if we edit iGPUBackend directly... int iTempGPUBackend; diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h new file mode 100644 index 0000000000..991e52f1fd --- /dev/null +++ b/GPU/Common/ShaderCommon.h @@ -0,0 +1,29 @@ +// Copyright (c) 2015- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +enum DebugShaderType { + SHADER_TYPE_VERTEX = 0, + SHADER_TYPE_FRAGMENT = 1, +}; + +enum DebugShaderStringType { + SHADER_STRING_SHORT_DESC = 0, + SHADER_STRING_SOURCE_CODE = 1, + SHADER_STRING_STATS = 2, +}; diff --git a/GPU/GLES/FragmentShaderGenerator.cpp b/GPU/GLES/FragmentShaderGenerator.cpp index 20a09bd314..1c16eb0632 100644 --- a/GPU/GLES/FragmentShaderGenerator.cpp +++ b/GPU/GLES/FragmentShaderGenerator.cpp @@ -24,6 +24,7 @@ #endif #include +#include #include "base/logging.h" #include "gfx_es2/gpu_features.h" @@ -330,6 +331,38 @@ enum { BIT_FLATSHADE = 46, }; +std::string FragmentShaderDesc(const ShaderID &id) { + std::stringstream desc; + if (id.Bit(BIT_CLEARMODE)) desc << "Clear "; + if (id.Bit(BIT_DO_TEXTURE)) desc << "Tex "; + if (id.Bit(BIT_DO_TEXTURE_PROJ)) desc << "TexProj "; + if (id.Bit(BIT_FLIP_TEXTURE)) desc << "Flip "; + if (id.Bit(BIT_TEXALPHA)) desc << "TexAlpha "; + if (id.Bit(BIT_TEXTURE_AT_OFFSET)) desc << "TexOffset "; + if (id.Bit(BIT_LMODE)) desc << "LM "; + if (id.Bit(BIT_ENABLE_FOG)) desc << "Fog "; + if (id.Bit(BIT_COLOR_DOUBLE)) desc << "Double "; + if (id.Bit(BIT_FLATSHADE)) desc << "Flat "; + if (id.Bit(BIT_SHADER_TEX_CLAMP)) desc << "Texclamp"; + if (id.Bit(BIT_CLAMP_S)) desc << "Clamp S "; + if (id.Bit(BIT_CLAMP_T)) desc << "Clamp T "; + + switch (id.Bits(BIT_STENCIL_TO_ALPHA, 2)) { + case REPLACE_ALPHA_NO: break; + case REPLACE_ALPHA_YES: desc << "StencilToAlpha "; break; + case REPLACE_ALPHA_DUALSOURCE: desc << "StencilToAlphaDualSrc "; break; + } + + if (id.Bit(BIT_ALPHA_TEST)) desc << "AlphaTest "; + if (id.Bit(BIT_ALPHA_AGAINST_ZERO)) desc << "AlphaTest0 "; + if (id.Bit(BIT_COLOR_TEST)) desc << "ColorTest "; + if (id.Bit(BIT_COLOR_AGAINST_ZERO)) desc << "ColorTest0 "; + + // TODO: A few more... + + return desc.str(); +} + // Here we must take all the bits of the gstate that determine what the fragment shader will // look like, and concatenate them together into an ID. void ComputeFragmentShaderID(ShaderID *id_out, uint32_t vertType) { diff --git a/GPU/GLES/FragmentShaderGenerator.h b/GPU/GLES/FragmentShaderGenerator.h index 7b7bf976ec..c4fab7dc9b 100644 --- a/GPU/GLES/FragmentShaderGenerator.h +++ b/GPU/GLES/FragmentShaderGenerator.h @@ -23,6 +23,7 @@ struct ShaderID; void ComputeFragmentShaderID(ShaderID *id, uint32_t vertType); bool GenerateFragmentShader(const ShaderID &id, char *buffer); +std::string FragmentShaderDesc(const ShaderID &id); enum StencilValueType { STENCIL_VALUE_UNIFORM, diff --git a/GPU/GLES/GLES_GPU.cpp b/GPU/GLES/GLES_GPU.cpp index f5a2d7e7a1..95a8b5ea52 100644 --- a/GPU/GLES/GLES_GPU.cpp +++ b/GPU/GLES/GLES_GPU.cpp @@ -2411,3 +2411,10 @@ bool GLES_GPU::DescribeCodePtr(const u8 *ptr, std::string &name) { } return false; } + +std::vector GLES_GPU::DebugGetShaderIDs(DebugShaderType type) { + return shaderManager_->DebugGetShaderIDs(type); +} +std::string GLES_GPU::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) { + return shaderManager_->DebugGetShaderString(id, type, stringType); +} diff --git a/GPU/GLES/GLES_GPU.h b/GPU/GLES/GLES_GPU.h index ec41984c2b..9e8af75484 100644 --- a/GPU/GLES/GLES_GPU.h +++ b/GPU/GLES/GLES_GPU.h @@ -150,6 +150,10 @@ public: void Execute_BoneMtxData(u32 op, u32 diff); void Execute_BlockTransferStart(u32 op, u32 diff); + // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. + std::vector DebugGetShaderIDs(DebugShaderType shader) override; + std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; + protected: void FastRunLoop(DisplayList &list) override; void ProcessEvent(GPUEvent ev) override; diff --git a/GPU/GLES/ShaderManager.cpp b/GPU/GLES/ShaderManager.cpp index fc78cb2331..440ee7750e 100644 --- a/GPU/GLES/ShaderManager.cpp +++ b/GPU/GLES/ShaderManager.cpp @@ -41,9 +41,10 @@ #include "Framebuffer.h" #include "i18n/i18n.h" -Shader::Shader(const char *code, uint32_t shaderType, bool useHWTransform, const ShaderID &shaderID) +Shader::Shader(const char *code, uint32_t glShaderType, bool useHWTransform, const ShaderID &shaderID) : id_(shaderID), failed_(false), useHWTransform_(useHWTransform) { PROFILE_THIS_SCOPE("shadercomp"); + isFragment_ = glShaderType == GL_FRAGMENT_SHADER; source_ = code; #ifdef SHADERLOG #ifdef _WIN32 @@ -52,7 +53,7 @@ Shader::Shader(const char *code, uint32_t shaderType, bool useHWTransform, const printf("%s\n", code); #endif #endif - shader = glCreateShader(shaderType); + shader = glCreateShader(glShaderType); glShaderSource(shader, 1, &code, 0); glCompileShader(shader); GLint success = 0; @@ -130,13 +131,17 @@ LinkedShader::LinkedShader(Shader *vs, Shader *fs, u32 vertType, bool useHWTrans ELOG("Could not link program:\n %s", buf); #endif ERROR_LOG(G3D, "Could not link program:\n %s", buf); - ERROR_LOG(G3D, "VS:\n%s", vs->source().c_str()); - ERROR_LOG(G3D, "FS:\n%s", fs->source().c_str()); - Reporting::ReportMessage("Error in shader program link: info: %s / fs: %s / vs: %s", buf, fs->source().c_str(), vs->source().c_str()); + ERROR_LOG(G3D, "VS desc:\n%s\n", vs->GetShaderString(SHADER_STRING_SHORT_DESC).c_str()); + ERROR_LOG(G3D, "FS desc:\n%s\n", fs->GetShaderString(SHADER_STRING_SHORT_DESC).c_str()); + std::string vs_source = vs->GetShaderString(SHADER_STRING_SOURCE_CODE); + std::string fs_source = fs->GetShaderString(SHADER_STRING_SOURCE_CODE); + ERROR_LOG(G3D, "VS:\n%s\n", vs_source.c_str()); + ERROR_LOG(G3D, "FS:\n%s\n", fs_source.c_str()); + Reporting::ReportMessage("Error in shader program link: info: %s / fs: %s / vs: %s", buf, fs_source.c_str(), vs_source.c_str()); #ifdef SHADERLOG OutputDebugStringUTF8(buf); - OutputDebugStringUTF8(vs->source().c_str()); - OutputDebugStringUTF8(fs->source().c_str()); + OutputDebugStringUTF8(vs_source.c_str()); + OutputDebugStringUTF8(fs_source.c_str()); #endif delete [] buf; // we're dead! } @@ -879,3 +884,64 @@ LinkedShader *ShaderManager::ApplyFragmentShader(Shader *vs, int prim, u32 vertT lastShader_ = ls; return ls; } + +std::string Shader::GetShaderString(DebugShaderStringType type) const { + switch (type) { + case SHADER_STRING_SOURCE_CODE: + return source_; + case SHADER_STRING_SHORT_DESC: + return isFragment_ ? FragmentShaderDesc(id_) : VertexShaderDesc(id_); + default: + return "N/A"; + } +} + +std::vector ShaderManager::DebugGetShaderIDs(DebugShaderType type) { + std::string id; + std::vector ids; + switch (type) { + case SHADER_TYPE_VERTEX: + { + for (auto iter : vsCache_) { + iter.first.ToString(&id); + ids.push_back(id); + } + } + break; + case SHADER_TYPE_FRAGMENT: + { + for (auto iter : fsCache_) { + iter.first.ToString(&id); + ids.push_back(id); + } + } + break; + } + return ids; +} + +std::string ShaderManager::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) { + ShaderID shaderId; + shaderId.FromString(id); + switch (type) { + case SHADER_TYPE_VERTEX: + { + auto iter = vsCache_.find(shaderId); + if (iter == vsCache_.end()) { + return ""; + } + return iter->second->GetShaderString(stringType); + } + + case SHADER_TYPE_FRAGMENT: + { + auto iter = fsCache_.find(shaderId); + if (iter == fsCache_.end()) { + return ""; + } + return iter->second->GetShaderString(stringType); + } + default: + return "N/A"; + } +} diff --git a/GPU/GLES/ShaderManager.h b/GPU/GLES/ShaderManager.h index 8e963a123a..e667ab963a 100644 --- a/GPU/GLES/ShaderManager.h +++ b/GPU/GLES/ShaderManager.h @@ -20,8 +20,10 @@ #include "base/basictypes.h" #include "Globals.h" #include -#include "VertexShaderGenerator.h" -#include "FragmentShaderGenerator.h" + +#include "GPU/Common/ShaderCommon.h" +#include "GPU/GLES/VertexShaderGenerator.h" +#include "GPU/GLES/FragmentShaderGenerator.h" class Shader; @@ -30,12 +32,12 @@ struct ShaderID { clear(); } void clear() { - for (int i = 0; i < ARRAY_SIZE(d); i++) { + for (size_t i = 0; i < ARRAY_SIZE(d); i++) { d[i] = 0; } } void set_invalid() { - for (int i = 0; i < ARRAY_SIZE(d); i++) { + for (size_t i = 0; i < ARRAY_SIZE(d); i++) { d[i] = 0xFFFFFFFF; } } @@ -77,6 +79,14 @@ struct ShaderID { d[bit >> 5] |= (value & mask) << (bit & 31); } } + + void ToString(std::string *dest) const { + dest->resize(sizeof(d)); + memcpy(&(*dest)[0], d, sizeof(d)); + } + void FromString(std::string src) { + memcpy(d, &(src)[0], sizeof(d)); + } }; // Pre-fetched attrs and uniforms @@ -213,20 +223,22 @@ enum { class Shader { public: - Shader(const char *code, uint32_t shaderType, bool useHWTransform, const ShaderID &shaderID); + Shader(const char *code, uint32_t glShaderType, bool useHWTransform, const ShaderID &shaderID); ~Shader(); uint32_t shader; - const std::string &source() const { return source_; } bool Failed() const { return failed_; } bool UseHWTransform() const { return useHWTransform_; } const ShaderID &ID() const { return id_; } + std::string GetShaderString(DebugShaderStringType type) const; + private: std::string source_; ShaderID id_; bool failed_; bool useHWTransform_; + bool isFragment_; }; class ShaderManager { @@ -251,6 +263,9 @@ public: int NumFragmentShaders() const { return (int)fsCache_.size(); } int NumPrograms() const { return (int)linkedShaderCache_.size(); } + std::vector DebugGetShaderIDs(DebugShaderType type); + std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); + private: void Clear(); static bool DebugAreShadersCompatibleForLinking(Shader *vs, Shader *fs); diff --git a/GPU/GLES/VertexShaderGenerator.cpp b/GPU/GLES/VertexShaderGenerator.cpp index 6fad076ec9..902110f7c9 100644 --- a/GPU/GLES/VertexShaderGenerator.cpp +++ b/GPU/GLES/VertexShaderGenerator.cpp @@ -43,9 +43,8 @@ #define WRITE p+=sprintf -// These bits are internal to this file, although the resulting IDs will be externally visible. -// TODO: We will cut away many of these, turning them into uniforms. This should reduce the number -// of generated shaders drastically. +// TODO: There will be additional bits, indicating that groups of these will be +// sent to the shader and processed there. This will cut down the number of shaders ("ubershader approach") enum { BIT_LMODE = 0, BIT_IS_THROUGH = 1, @@ -57,32 +56,71 @@ enum { BIT_USE_HW_TRANSFORM = 8, BIT_HAS_NORMAL = 9, // conditioned on hw transform BIT_NORM_REVERSE = 10, - BIT_HAS_TEXCOORD = 11, + BIT_HAS_TEXCOORD = 11, // 5 free after BIT_UVGEN_MODE = 16, BIT_UVPROJ_MODE = 18, // 2, can overlap with LS0 BIT_LS0 = 18, // 2 BIT_LS1 = 20, // 2 BIT_BONES = 22, // 3 should be enough, not 8 BIT_ENABLE_BONES = 30, - BIT_LIGHT0_COMP = 32, - BIT_LIGHT0_TYPE = 34, - BIT_LIGHT1_COMP = 36, - BIT_LIGHT1_TYPE = 38, - BIT_LIGHT2_COMP = 40, - BIT_LIGHT2_TYPE = 42, - BIT_LIGHT3_COMP = 44, - BIT_LIGHT3_TYPE = 46, - BIT_MATERIAL_UPDATE = 48, + BIT_LIGHT0_COMP = 32, // 2 bits + BIT_LIGHT0_TYPE = 34, // 2 bits + BIT_LIGHT1_COMP = 36, // 2 bits + BIT_LIGHT1_TYPE = 38, // 2 bits + BIT_LIGHT2_COMP = 40, // 2 bits + BIT_LIGHT2_TYPE = 42, // 2 bits + BIT_LIGHT3_COMP = 44, // 2 bits + BIT_LIGHT3_TYPE = 46, // 2 bits + BIT_MATERIAL_UPDATE = 48, // 3 bits, 1 free after BIT_LIGHT0_ENABLE = 52, BIT_LIGHT1_ENABLE = 53, BIT_LIGHT2_ENABLE = 54, BIT_LIGHT3_ENABLE = 55, BIT_LIGHTING_ENABLE = 56, - BIT_WEIGHT_FMTSCALE = 57, + BIT_WEIGHT_FMTSCALE = 57, // only two bits, 1 free after BIT_TEXCOORD_FMTSCALE = 60, - BIT_FLATSHADE = 62, + BIT_FLATSHADE = 62, // 1 free after }; +std::string VertexShaderDesc(const ShaderID &id) { + std::stringstream desc; + if (id.Bit(BIT_IS_THROUGH)) desc << "THR "; + if (id.Bit(BIT_USE_HW_TRANSFORM)) desc << "HWX "; + if (id.Bit(BIT_HAS_COLOR)) desc << "C "; + if (id.Bit(BIT_HAS_TEXCOORD)) desc << "T "; + if (id.Bit(BIT_HAS_NORMAL)) desc << "N "; + if (id.Bit(BIT_LMODE)) desc << "LM "; + if (id.Bit(BIT_ENABLE_FOG)) desc << "Fog "; + if (id.Bit(BIT_NORM_REVERSE)) desc << "RevN "; + if (id.Bit(BIT_DO_TEXTURE)) desc << "Tex "; + if (id.Bit(BIT_DO_TEXTURE_PROJ)) desc << "TexProj "; + if (id.Bit(BIT_FLIP_TEXTURE)) desc << "Flip "; + int uvgMode = id.Bits(BIT_UVGEN_MODE, 2); + const char *uvgModes[4] = { "UV ", "UVMtx ", "UVEnv ", "UVUnk " }; + + if (uvgMode) desc << uvgModes[uvgMode]; + if (id.Bit(BIT_ENABLE_BONES)) desc << "Bones:" << (id.Bits(BIT_BONES, 3) + 1) << " "; + // Lights + if (id.Bit(BIT_LIGHTING_ENABLE)) { + desc << "Light: "; + for (int i = 0; i < 4; i++) { + if (id.Bit(BIT_LIGHT0_ENABLE + i)) { + desc << i << ": "; + desc << "c:" << id.Bits(BIT_LIGHT0_COMP + 4 * i, 2) << " t:" << id.Bits(BIT_LIGHT0_TYPE + 4 * i, 2) << " "; + } + } + } + desc << "MatUp:" << id.Bits(BIT_MATERIAL_UPDATE, 3) << " "; + + desc << "WScale " << id.Bits(BIT_WEIGHT_FMTSCALE, 2) << " "; + desc << "TCScale " << id.Bits(BIT_TEXCOORD_FMTSCALE, 2) << " "; + + if (id.Bit(BIT_FLATSHADE)) desc << "Flat "; + + // TODO: More... + return desc.str(); +} + bool CanUseHardwareTransform(int prim) { if (!g_Config.bHardwareTransform) return false; @@ -118,7 +156,7 @@ void ComputeVertexShaderID(ShaderID *id_out, u32 vertType, bool useHWTransform) id.SetBit(BIT_USE_HW_TRANSFORM); id.SetBit(BIT_HAS_NORMAL, hasNormal); - // UV generation mode. + // UV generation mode. doShadeMapping is implicitly stored here. id.SetBits(BIT_UVGEN_MODE, 2, gstate.getUVGenMode()); // The next bits are used differently depending on UVgen mode @@ -138,20 +176,17 @@ void ComputeVertexShaderID(ShaderID *id_out, u32 vertType, bool useHWTransform) // Okay, d[1] coming up. ============== if (gstate.isLightingEnabled() || doShadeMapping) { + // doShadeMapping is stored as UVGenMode, so this is enough for isLightingEnabled. + id.SetBit(BIT_LIGHTING_ENABLE); // Light bits for (int i = 0; i < 4; i++) { + id.SetBit(BIT_LIGHT0_ENABLE + i, gstate.isLightChanEnabled(i) != 0); if (gstate.isLightChanEnabled(i) || (doShadeMapping && (gstate.getUVLS0() == i || gstate.getUVLS1() == i))) { id.SetBits(BIT_LIGHT0_COMP + 4 * i, 2, gstate.getLightComputation(i)); id.SetBits(BIT_LIGHT0_TYPE + 4 * i, 2, gstate.getLightType(i)); } } id.SetBits(BIT_MATERIAL_UPDATE, 3, gstate.getMaterialUpdate() & 7); - // TODO: Optimize by shifting in all the light bits together. - for (int i = 0; i < 4; i++) { - id.SetBit(BIT_LIGHT0_ENABLE + i, gstate.isLightChanEnabled(i) != 0); - } - // doShadeMapping is stored as UVGenMode, so this is enough for isLightingEnabled. - id.SetBit(BIT_LIGHTING_ENABLE); } // 2 bits. We should probably send in the weight scalefactor as a uniform instead, @@ -159,7 +194,7 @@ void ComputeVertexShaderID(ShaderID *id_out, u32 vertType, bool useHWTransform) id.SetBits(BIT_WEIGHT_FMTSCALE, 2, vertTypeGetWeightMask(vertType)); id.SetBit(BIT_NORM_REVERSE, gstate.areNormalsReversed()); if (doTextureProjection && gstate.getUVProjMode() == GE_PROJMAP_UV) { - id.SetBit(BIT_TEXCOORD_FMTSCALE, (vertType & GE_VTYPE_TC_MASK) >> GE_VTYPE_TC_SHIFT); // two bits + id.SetBits(BIT_TEXCOORD_FMTSCALE, 2, (vertType & GE_VTYPE_TC_MASK) >> GE_VTYPE_TC_SHIFT); // two bits } else { id.SetBit(BIT_HAS_TEXCOORD, hasTexcoord); } @@ -406,7 +441,6 @@ void GenerateVertexShader(const ShaderID &id, char *buffer) { WRITE(p, "uniform lowp vec4 u_ambient;\n"); if ((matUpdate & 2) == 0 || !hasColor) WRITE(p, "uniform lowp vec3 u_matdiffuse;\n"); - // if ((gstate.materialupdate & 4) == 0) WRITE(p, "uniform lowp vec4 u_matspecular;\n"); // Specular coef is contained in alpha WRITE(p, "uniform lowp vec3 u_matemissive;\n"); } diff --git a/GPU/GLES/VertexShaderGenerator.h b/GPU/GLES/VertexShaderGenerator.h index 544686df1a..aa1596f308 100644 --- a/GPU/GLES/VertexShaderGenerator.h +++ b/GPU/GLES/VertexShaderGenerator.h @@ -17,7 +17,7 @@ #pragma once -#include "Globals.h" +#include "Common/CommonTypes.h" // #define USE_BONE_ARRAY @@ -27,3 +27,7 @@ bool CanUseHardwareTransform(int prim); void ComputeVertexShaderID(ShaderID *id, u32 vertexType, bool useHWTransform); void GenerateVertexShader(const ShaderID &id, char *buffer); + +// Generates a compact string that describes the shader. Useful in a list to get an overview +// of the current flora of shaders. +std::string VertexShaderDesc(const ShaderID &id); diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index b0fec7667c..791a089566 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -186,6 +186,7 @@ + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index 60c9232ff4..c48555d3ac 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -195,6 +195,9 @@ Common + + Common + @@ -375,4 +378,4 @@ GLES - + \ No newline at end of file diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 87d4d8d84d..9dd34fb14d 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -120,6 +120,11 @@ public: virtual void ClearShaderCache() {} virtual void CleanupBeforeUI() {} + std::vector DebugGetShaderIDs(DebugShaderType shader) override { return std::vector(); }; + std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override { + return "N/A"; + } + protected: // To avoid virtual calls to PreExecuteOp(). virtual void FastRunLoop(DisplayList &list) = 0; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 45cad84145..570bb414eb 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -24,6 +24,7 @@ #include "GPU/GPU.h" #include "Core/MemMap.h" #include "GPU/ge_constants.h" +#include "GPU/Common/ShaderCommon.h" struct PspGeListArgs; struct GPUgstate; @@ -285,4 +286,8 @@ public: virtual const std::list& GetDisplayLists() = 0; virtual bool DecodeTexture(u8* dest, const GPUgstate &state) = 0; virtual std::vector GetFramebufferList() = 0; + + // For debugging. The IDs returned are opaque, do not poke in them or display them in any way. + virtual std::vector DebugGetShaderIDs(DebugShaderType type) = 0; + virtual std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) = 0; }; diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 037307ae99..3c4aaa9f2d 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -67,6 +67,7 @@ void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { parent->Add(new Choice(dev->T("Logging Channels")))->OnClick.Handle(this, &DevMenu::OnLogConfig); parent->Add(new Choice(sy->T("Developer Tools")))->OnClick.Handle(this, &DevMenu::OnDeveloperTools); parent->Add(new Choice(dev->T("Jit Compare")))->OnClick.Handle(this, &DevMenu::OnJitCompare); + parent->Add(new Choice(dev->T("Shader Viewer")))->OnClick.Handle(this, &DevMenu::OnShaderView); parent->Add(new Choice(dev->T("Toggle Freeze")))->OnClick.Handle(this, &DevMenu::OnFreezeFrame); parent->Add(new Choice(dev->T("Dump Frame GPU Commands")))->OnClick.Handle(this, &DevMenu::OnDumpFrame); parent->Add(new Choice(dev->T("Toggle Audio Debug")))->OnClick.Handle(this, &DevMenu::OnToggleAudioDebug); @@ -110,6 +111,14 @@ UI::EventReturn DevMenu::OnJitCompare(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn DevMenu::OnShaderView(UI::EventParams &e) { + UpdateUIState(UISTATE_PAUSEMENU); + screenManager()->push(new ShaderListScreen()); + return UI::EVENT_DONE; +} + + + UI::EventReturn DevMenu::OnFreezeFrame(UI::EventParams &e) { if (PSP_CoreParameter().frozen) { PSP_CoreParameter().frozen = false; @@ -761,7 +770,6 @@ void JitCompareScreen::OnRandomBlock(int flag) { UpdateDisasm(); } - UI::EventReturn JitCompareScreen::OnCurrentBlock(UI::EventParams &e) { if (!MIPSComp::jit) { return UI::EVENT_DONE; @@ -778,173 +786,74 @@ UI::EventReturn JitCompareScreen::OnCurrentBlock(UI::EventParams &e) { return UI::EVENT_DONE; } -static const uint32_t nice_colors[] = { - 0xFF8040, - 0x80FF40, - 0x8040FF, - 0xFFFF40, - - 0x40FFFF, - 0xFF70FF, - 0xc0c0c0, - 0xb040c0, - - 0x184099, - 0xCC3333, - 0xFF99CC, - 0x3399CC, - - 0x990000, - 0x003366, - 0xF8F8F8, - 0x33FFFF, -}; - -enum ProfileCatStatus { - PROFILE_CAT_VISIBLE = 0, - PROFILE_CAT_IGNORE = 1, - PROFILE_CAT_NOLEGEND = 2, -}; - -void DrawProfile(UIContext &ui) { -#ifdef USE_PROFILER - int numCategories = Profiler_GetNumCategories(); - int historyLength = Profiler_GetHistoryLength(); - - ui.SetFontStyle(ui.theme->uiFont); - - static float lastMaxVal = 1.0f / 60.0f; - float legendMinVal = lastMaxVal * (1.0f / 120.0f); - - std::vector history; - std::vector catStatus; - history.resize(historyLength); - catStatus.resize(numCategories); - - float rowH = 30.0f; - float legendHeight = 0.0f; - float legendWidth = 80.0f; - for (int i = 0; i < numCategories; i++) { - const char *name = Profiler_GetCategoryName(i); - if (!strcmp(name, "timing")) { - catStatus[i] = PROFILE_CAT_IGNORE; - continue; - } - - Profiler_GetHistory(i, &history[0], historyLength); - catStatus[i] = PROFILE_CAT_NOLEGEND; - for (int j = 0; j < historyLength; ++j) { - if (history[j] > legendMinVal) { - catStatus[i] = PROFILE_CAT_VISIBLE; - break; - } - } - - // So they don't move horizontally, we always measure. - float w = 0.0f, h = 0.0f; - ui.MeasureText(ui.GetFontStyle(), name, &w, &h); - if (w > legendWidth) { - legendWidth = w; - } - legendHeight += rowH; +void ShaderListScreen::ListShaders(DebugShaderType shaderType, UI::LinearLayout *view) { + using namespace UI; + std::vector shaderIds_ = gpu->DebugGetShaderIDs(shaderType); + for (auto id : shaderIds_) { + Choice *choice = view->Add(new Choice(gpu->DebugGetShaderString(id, shaderType, SHADER_STRING_SHORT_DESC))); + choice->SetTag(id); + choice->OnClick.Handle(this, &ShaderListScreen::OnShaderClick); } - legendWidth += 20.0f; - - float legendStartY = legendHeight > ui.GetBounds().centerY() ? ui.GetBounds().y2() - legendHeight : ui.GetBounds().centerY(); - float legendStartX = ui.GetBounds().x2() - std::min(legendWidth, 200.0f); - - const uint32_t opacity = 140 << 24; - - int legendNum = 0; - for (int i = 0; i < numCategories; i++) { - const char *name = Profiler_GetCategoryName(i); - uint32_t color = nice_colors[i % ARRAY_SIZE(nice_colors)]; - - if (catStatus[i] == PROFILE_CAT_VISIBLE) { - float y = legendStartY + legendNum++ * rowH; - ui.FillRect(UI::Drawable(opacity | color), Bounds(legendStartX, y, rowH - 2, rowH - 2)); - ui.DrawTextShadow(name, legendStartX + rowH + 2, y, 0xFFFFFFFF, ALIGN_VBASELINE); - } - } - - float graphWidth = ui.GetBounds().x2() - legendWidth - 20.0f; - float graphHeight = ui.GetBounds().h * 0.8f; - - float dx = graphWidth / historyLength; - - /* - ui.Flush(); - - ui.BeginNoTex(); - */ - - bool area = true; - float minVal = 0.0f; - float maxVal = lastMaxVal; // TODO - adjust to frame length - if (maxVal < 0.001f) - maxVal = 0.001f; - if (maxVal > 1.0f / 15.0f) - maxVal = 1.0f / 15.0f; - - float scale = (graphHeight) / (maxVal - minVal); - - float y_60th = ui.GetBounds().y2() - 10 - (1.0f / 60.0f) * scale; - float y_1ms = ui.GetBounds().y2() - 10 - (1.0f / 1000.0f) * scale; - - ui.FillRect(UI::Drawable(0x80FFFF00), Bounds(0, y_60th, graphWidth, 2)); - ui.FillRect(UI::Drawable(0x80FFFF00), Bounds(0, y_1ms, graphWidth, 2)); - ui.DrawTextShadow("1/60s", 5, y_60th, 0x80FFFF00); - ui.DrawTextShadow("1ms", 5, y_1ms, 0x80FFFF00); - - std::vector total; - total.resize(historyLength); - - maxVal = 0.0f; - float maxTotal = 0.0f; - for (int i = 0; i < numCategories; i++) { - if (catStatus[i] == PROFILE_CAT_IGNORE) { - continue; - } - Profiler_GetHistory(i, &history[0], historyLength); - - float x = 10; - uint32_t col = nice_colors[i % ARRAY_SIZE(nice_colors)]; - if (area) - col = opacity | (col & 0xFFFFFF); - UI::Drawable color(col); - UI::Drawable outline((opacity >> 1) | 0xFFFFFF); - - if (area) { - for (int n = 0; n < historyLength; n++) { - float val = history[n]; - float valY1 = ui.GetBounds().y2() - 10 - (val + total[n]) * scale; - float valY2 = ui.GetBounds().y2() - 10 - total[n] * scale; - ui.FillRect(outline, Bounds(x, valY2, dx, 1.0f)); - ui.FillRect(color, Bounds(x, valY1, dx, valY2 - valY1)); - x += dx; - total[n] += val; - } - } else { - for (int n = 0; n < historyLength; n++) { - float val = history[n]; - if (val > maxVal) - maxVal = val; - float valY = ui.GetBounds().y2() - 10 - history[n] * scale; - ui.FillRect(color, Bounds(x, valY, dx, 5)); - x += dx; - } - } - } - - for (int n = 0; n < historyLength; n++) { - if (total[n] > maxTotal) - maxTotal = total[n]; - } - - if (area) { - maxVal = maxTotal; - } - - lastMaxVal = lastMaxVal * 0.95f + maxVal * 0.05f; -#endif +} + +void ShaderListScreen::CreateViews() { + using namespace UI; + + LinearLayout *layout = new LinearLayout(ORIENT_VERTICAL); + root_ = layout; + + tabs_ = new TabHolder(ORIENT_HORIZONTAL, 40, new LinearLayoutParams(1.0)); + layout->Add(tabs_); + layout->Add(new Button("Back"))->OnClick.Handle(this, &UIScreen::OnBack); + + ScrollView *vs_scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); + ScrollView *fs_scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); + + LinearLayout *vshaderList = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT)); + LinearLayout *fshaderList = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT)); + + ListShaders(SHADER_TYPE_VERTEX, vshaderList); + ListShaders(SHADER_TYPE_FRAGMENT, fshaderList); + + vs_scroll->Add(vshaderList); + fs_scroll->Add(fshaderList); + + tabs_->AddTab("Vertex", vs_scroll); + tabs_->AddTab("Fragment", fs_scroll); +} + +UI::EventReturn ShaderListScreen::OnShaderClick(UI::EventParams &e) { + using namespace UI; + std::string id = e.v->Tag(); + DebugShaderType type = SHADER_TYPE_VERTEX; + if (tabs_->GetCurrentTab() == 1) { + type = SHADER_TYPE_FRAGMENT; + } + screenManager()->push(new ShaderViewScreen(id, type)); + return EVENT_DONE; +} + +void ShaderViewScreen::CreateViews() { + using namespace UI; + + LinearLayout *layout = new LinearLayout(ORIENT_VERTICAL); + root_ = layout; + + layout->Add(new TextView(gpu->DebugGetShaderString(id_, type_, SHADER_STRING_SHORT_DESC))); + + ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); + layout->Add(scroll); + + LinearLayout *lineLayout = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + lineLayout->SetSpacing(0.0); + scroll->Add(lineLayout); + + std::vector lines; + SplitString(gpu->DebugGetShaderString(id_, type_, SHADER_STRING_SOURCE_CODE), '\n', lines); + + for (auto line : lines) { + lineLayout->Add(new TextView(line, FLAG_DYNAMIC_ASCII, true)); + } + + layout->Add(new Button("Back"))->OnClick.Handle(this, &UIScreen::OnBack); } diff --git a/UI/DevScreens.h b/UI/DevScreens.h index c19f83522b..42130a4cd9 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -26,6 +26,7 @@ #include "ui/ui_screen.h" #include "UI/MiscScreens.h" +#include "GPU/Common/ShaderCommon.h" class DevMenu : public PopupScreen { public: @@ -38,6 +39,7 @@ protected: UI::EventReturn OnLogView(UI::EventParams &e); UI::EventReturn OnLogConfig(UI::EventParams &e); UI::EventReturn OnJitCompare(UI::EventParams &e); + UI::EventReturn OnShaderView(UI::EventParams &e); UI::EventReturn OnFreezeFrame(UI::EventParams &e); UI::EventReturn OnDumpFrame(UI::EventParams &e); UI::EventReturn OnDeveloperTools(UI::EventParams &e); @@ -141,5 +143,28 @@ private: UI::LinearLayout *rightDisasm_; }; +class ShaderListScreen : public UIDialogScreenWithBackground { +public: + ShaderListScreen() {} + void CreateViews() override; + +private: + void ListShaders(DebugShaderType shaderType, UI::LinearLayout *view); + + UI::EventReturn OnShaderClick(UI::EventParams &e); + + UI::TabHolder *tabs_; +}; + +class ShaderViewScreen : public UIDialogScreenWithBackground { +public: + ShaderViewScreen(std::string id, DebugShaderType type) + : id_(id), type_(type) {} + + void CreateViews() override; +private: + std::string id_; + DebugShaderType type_; +}; void DrawProfile(UIContext &ui); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 59e1792a83..bdc57b3adb 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -63,6 +63,7 @@ #include "UI/ControlMappingScreen.h" #include "UI/GameSettingsScreen.h" #include "UI/InstallZipScreen.h" +#include "UI/ProfilerDraw.h" EmuScreen::EmuScreen(const std::string &filename) : bootPending_(true), gamePath_(filename), invalid_(true), quit_(false), pauseTrigger_(false), saveStatePreviewShownTime_(0.0), saveStatePreview_(nullptr) { diff --git a/UI/ProfilerDraw.cpp b/UI/ProfilerDraw.cpp new file mode 100644 index 0000000000..14c72733d5 --- /dev/null +++ b/UI/ProfilerDraw.cpp @@ -0,0 +1,192 @@ +// Copyright (c) 2014- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include + +#include "ui/ui_context.h" +#include "profiler/profiler.h" + +static const uint32_t nice_colors[] = { + 0xFF8040, + 0x80FF40, + 0x8040FF, + 0xFFFF40, + + 0x40FFFF, + 0xFF70FF, + 0xc0c0c0, + 0xb040c0, + + 0x184099, + 0xCC3333, + 0xFF99CC, + 0x3399CC, + + 0x990000, + 0x003366, + 0xF8F8F8, + 0x33FFFF, +}; + +enum ProfileCatStatus { + PROFILE_CAT_VISIBLE = 0, + PROFILE_CAT_IGNORE = 1, + PROFILE_CAT_NOLEGEND = 2, +}; + +void DrawProfile(UIContext &ui) { +#ifdef USE_PROFILER + int numCategories = Profiler_GetNumCategories(); + int historyLength = Profiler_GetHistoryLength(); + + ui.SetFontStyle(ui.theme->uiFont); + + static float lastMaxVal = 1.0f / 60.0f; + float legendMinVal = lastMaxVal * (1.0f / 120.0f); + + std::vector history; + std::vector catStatus; + history.resize(historyLength); + catStatus.resize(numCategories); + + float rowH = 30.0f; + float legendHeight = 0.0f; + float legendWidth = 80.0f; + for (int i = 0; i < numCategories; i++) { + const char *name = Profiler_GetCategoryName(i); + if (!strcmp(name, "timing")) { + catStatus[i] = PROFILE_CAT_IGNORE; + continue; + } + + Profiler_GetHistory(i, &history[0], historyLength); + catStatus[i] = PROFILE_CAT_NOLEGEND; + for (int j = 0; j < historyLength; ++j) { + if (history[j] > legendMinVal) { + catStatus[i] = PROFILE_CAT_VISIBLE; + break; + } + } + + // So they don't move horizontally, we always measure. + float w = 0.0f, h = 0.0f; + ui.MeasureText(ui.GetFontStyle(), name, &w, &h); + if (w > legendWidth) { + legendWidth = w; + } + legendHeight += rowH; + } + legendWidth += 20.0f; + + float legendStartY = legendHeight > ui.GetBounds().centerY() ? ui.GetBounds().y2() - legendHeight : ui.GetBounds().centerY(); + float legendStartX = ui.GetBounds().x2() - std::min(legendWidth, 200.0f); + + const uint32_t opacity = 140 << 24; + + int legendNum = 0; + for (int i = 0; i < numCategories; i++) { + const char *name = Profiler_GetCategoryName(i); + uint32_t color = nice_colors[i % ARRAY_SIZE(nice_colors)]; + + if (catStatus[i] == PROFILE_CAT_VISIBLE) { + float y = legendStartY + legendNum++ * rowH; + ui.FillRect(UI::Drawable(opacity | color), Bounds(legendStartX, y, rowH - 2, rowH - 2)); + ui.DrawTextShadow(name, legendStartX + rowH + 2, y, 0xFFFFFFFF, ALIGN_VBASELINE); + } + } + + float graphWidth = ui.GetBounds().x2() - legendWidth - 20.0f; + float graphHeight = ui.GetBounds().h * 0.8f; + + float dx = graphWidth / historyLength; + + /* + ui.Flush(); + + ui.BeginNoTex(); + */ + + bool area = true; + float minVal = 0.0f; + float maxVal = lastMaxVal; // TODO - adjust to frame length + if (maxVal < 0.001f) + maxVal = 0.001f; + if (maxVal > 1.0f / 15.0f) + maxVal = 1.0f / 15.0f; + + float scale = (graphHeight) / (maxVal - minVal); + + float y_60th = ui.GetBounds().y2() - 10 - (1.0f / 60.0f) * scale; + float y_1ms = ui.GetBounds().y2() - 10 - (1.0f / 1000.0f) * scale; + + ui.FillRect(UI::Drawable(0x80FFFF00), Bounds(0, y_60th, graphWidth, 2)); + ui.FillRect(UI::Drawable(0x80FFFF00), Bounds(0, y_1ms, graphWidth, 2)); + ui.DrawTextShadow("1/60s", 5, y_60th, 0x80FFFF00); + ui.DrawTextShadow("1ms", 5, y_1ms, 0x80FFFF00); + + std::vector total; + total.resize(historyLength); + + maxVal = 0.0f; + float maxTotal = 0.0f; + for (int i = 0; i < numCategories; i++) { + if (catStatus[i] == PROFILE_CAT_IGNORE) { + continue; + } + Profiler_GetHistory(i, &history[0], historyLength); + + float x = 10; + uint32_t col = nice_colors[i % ARRAY_SIZE(nice_colors)]; + if (area) + col = opacity | (col & 0xFFFFFF); + UI::Drawable color(col); + UI::Drawable outline((opacity >> 1) | 0xFFFFFF); + + if (area) { + for (int n = 0; n < historyLength; n++) { + float val = history[n]; + float valY1 = ui.GetBounds().y2() - 10 - (val + total[n]) * scale; + float valY2 = ui.GetBounds().y2() - 10 - total[n] * scale; + ui.FillRect(outline, Bounds(x, valY2, dx, 1.0f)); + ui.FillRect(color, Bounds(x, valY1, dx, valY2 - valY1)); + x += dx; + total[n] += val; + } + } else { + for (int n = 0; n < historyLength; n++) { + float val = history[n]; + if (val > maxVal) + maxVal = val; + float valY = ui.GetBounds().y2() - 10 - history[n] * scale; + ui.FillRect(color, Bounds(x, valY, dx, 5)); + x += dx; + } + } + } + + for (int n = 0; n < historyLength; n++) { + if (total[n] > maxTotal) + maxTotal = total[n]; + } + + if (area) { + maxVal = maxTotal; + } + + lastMaxVal = lastMaxVal * 0.95f + maxVal * 0.05f; +#endif +} diff --git a/UI/ProfilerDraw.h b/UI/ProfilerDraw.h new file mode 100644 index 0000000000..4de6d4c806 --- /dev/null +++ b/UI/ProfilerDraw.h @@ -0,0 +1,9 @@ +#pragma once + +class UIContext; + +#ifdef USE_PROFILER + +void DrawProfile(UIContext &ui); + +#endif \ No newline at end of file diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 7257c95768..35117a14b1 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -33,6 +33,7 @@ + @@ -57,6 +58,7 @@ + @@ -217,4 +219,4 @@ - + \ No newline at end of file diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 84115f0088..e0a46833f3 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -56,6 +56,9 @@ Screens + + Screens + @@ -112,6 +115,9 @@ Screens + + Screens + diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 484a6c1b63..ed1bc1907f 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -562,7 +562,7 @@ namespace MainWindow static double lastMouseDown; double now = real_time_now(); if ((now - lastMouseDown) < 0.001 * GetDoubleClickTime()) { - if (!g_Config.bShowTouchControls && GetUIState() == UISTATE_INGAME) { + if (!g_Config.bShowTouchControls && GetUIState() == UISTATE_INGAME && g_Config.bFullscreenOnDoubleclick) { SendToggleFullscreen(!g_Config.bFullScreen); } lastMouseDown = 0.0; diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 74b5d5a934..52b876e9c1 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -349,6 +349,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/TouchControlVisibilityScreen.cpp \ $(SRC)/UI/CwCheatScreen.cpp \ $(SRC)/UI/InstallZipScreen.cpp \ + $(SRC)/UI/ProfilerDraw.cpp \ $(SRC)/UI/NativeApp.cpp ifneq ($(SKIPAPP),1)