1 // Copyright (c) 2014- PPSSPP Project.
2 
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6 
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 // GNU General Public License 2.0 for more details.
11 
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14 
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17 
18 #include "Common/GPU/thin3d.h"
19 #include "Common/GPU/OpenGL/GLDebugLog.h"
20 #include "Core/Config.h"
21 #include "GPU/GLES/FragmentTestCacheGLES.h"
22 #include "GPU/GPUState.h"
23 #include "GPU/Common/GPUStateUtils.h"
24 #include "GPU/Common/ShaderId.h"
25 
26 // These are small, let's give them plenty of frames.
27 static const int FRAGTEST_TEXTURE_OLD_AGE = 307;
28 static const int FRAGTEST_DECIMATION_INTERVAL = 113;
29 
FragmentTestCacheGLES(Draw::DrawContext * draw)30 FragmentTestCacheGLES::FragmentTestCacheGLES(Draw::DrawContext *draw) {
31 	render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
32 }
33 
~FragmentTestCacheGLES()34 FragmentTestCacheGLES::~FragmentTestCacheGLES() {
35 	Clear();
36 }
37 
DeviceRestore(Draw::DrawContext * draw)38 void FragmentTestCacheGLES::DeviceRestore(Draw::DrawContext *draw) {
39 	render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
40 }
41 
BindTestTexture(int slot)42 void FragmentTestCacheGLES::BindTestTexture(int slot) {
43 	if (!g_Config.bFragmentTestCache) {
44 		return;
45 	}
46 
47 	bool alphaNeedsTexture = gstate.isAlphaTestEnabled() && !IsAlphaTestAgainstZero() && !IsAlphaTestTriviallyTrue();
48 	bool colorNeedsTexture = gstate.isColorTestEnabled() && !IsColorTestAgainstZero() && !IsColorTestTriviallyTrue();
49 	if (!alphaNeedsTexture && !colorNeedsTexture) {
50 		// Common case: testing against zero.  Just skip it, faster not to bind anything.
51 		return;
52 	}
53 
54 	const FragmentTestID id = GenerateTestID();
55 	const auto cached = cache_.find(id);
56 	if (cached != cache_.end()) {
57 		cached->second.lastFrame = gpuStats.numFlips;
58 		GLRTexture *tex = cached->second.texture;
59 		if (tex == lastTexture_) {
60 			// Already bound, hurray.
61 			return;
62 		}
63 		render_->BindTexture(slot, tex);
64 		lastTexture_ = tex;
65 		return;
66 	}
67 
68 	const u8 rRef = (gstate.getColorTestRef() >> 0) & 0xFF;
69 	const u8 rMask = (gstate.getColorTestMask() >> 0) & 0xFF;
70 	const u8 gRef = (gstate.getColorTestRef() >> 8) & 0xFF;
71 	const u8 gMask = (gstate.getColorTestMask() >> 8) & 0xFF;
72 	const u8 bRef = (gstate.getColorTestRef() >> 16) & 0xFF;
73 	const u8 bMask = (gstate.getColorTestMask() >> 16) & 0xFF;
74 	const u8 aRef = gstate.getAlphaTestRef();
75 	const u8 aMask = gstate.getAlphaTestMask();
76 	const u8 refs[4] = {rRef, gRef, bRef, aRef};
77 	const u8 masks[4] = {rMask, gMask, bMask, aMask};
78 	const GEComparison funcs[4] = {gstate.getColorTestFunction(), gstate.getColorTestFunction(), gstate.getColorTestFunction(), gstate.getAlphaTestFunction()};
79 	const bool valid[4] = {gstate.isColorTestEnabled(), gstate.isColorTestEnabled(), gstate.isColorTestEnabled(), gstate.isAlphaTestEnabled()};
80 
81 	GLRTexture *tex = CreateTestTexture(funcs, refs, masks, valid);
82 	lastTexture_ = tex;
83 	render_->BindTexture(slot, tex);
84 	// We only need to do this once for the texture.
85 	render_->SetTextureSampler(slot, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_NEAREST, GL_NEAREST, 0.0f);
86 	FragmentTestTexture item;
87 	item.lastFrame = gpuStats.numFlips;
88 	item.texture = tex;
89 	cache_[id] = item;
90 }
91 
GenerateTestID() const92 FragmentTestID FragmentTestCacheGLES::GenerateTestID() const {
93 	FragmentTestID id;
94 	// Let's just keep it simple, it's all in here.
95 	id.alpha = gstate.isAlphaTestEnabled() ? gstate.alphatest : 0;
96 	if (gstate.isColorTestEnabled()) {
97 		id.colorRefFunc = gstate.getColorTestFunction() | (gstate.getColorTestRef() << 8);
98 		id.colorMask = gstate.getColorTestMask();
99 	} else {
100 		id.colorRefFunc = 0;
101 		id.colorMask = 0;
102 	}
103 	return id;
104 }
105 
CreateTestTexture(const GEComparison funcs[4],const u8 refs[4],const u8 masks[4],const bool valid[4])106 GLRTexture *FragmentTestCacheGLES::CreateTestTexture(const GEComparison funcs[4], const u8 refs[4], const u8 masks[4], const bool valid[4]) {
107 	u8 *data = new u8[256 * 4];
108 	// TODO: Might it be better to use GL_ALPHA for simple textures?
109 	// TODO: Experiment with 4-bit/etc. textures.
110 
111 	// Build the logic map.
112 	for (int color = 0; color < 256; ++color) {
113 		for (int i = 0; i < 4; ++i) {
114 			bool res = true;
115 			if (valid[i]) {
116 				switch (funcs[i]) {
117 				case GE_COMP_NEVER:
118 					res = false;
119 					break;
120 				case GE_COMP_ALWAYS:
121 					res = true;
122 					break;
123 				case GE_COMP_EQUAL:
124 					res = (color & masks[i]) == (refs[i] & masks[i]);
125 					break;
126 				case GE_COMP_NOTEQUAL:
127 					res = (color & masks[i]) != (refs[i] & masks[i]);
128 					break;
129 				case GE_COMP_LESS:
130 					res = (color & masks[i]) < (refs[i] & masks[i]);
131 					break;
132 				case GE_COMP_LEQUAL:
133 					res = (color & masks[i]) <= (refs[i] & masks[i]);
134 					break;
135 				case GE_COMP_GREATER:
136 					res = (color & masks[i]) > (refs[i] & masks[i]);
137 					break;
138 				case GE_COMP_GEQUAL:
139 					res = (color & masks[i]) >= (refs[i] & masks[i]);
140 					break;
141 				}
142 			}
143 			data[color * 4 + i] = res ? 0xFF : 0;
144 		}
145 	}
146 
147 	GLRTexture *tex = render_->CreateTexture(GL_TEXTURE_2D, 256, 1, 1);
148 	render_->TextureImage(tex, 0, 256, 1, Draw::DataFormat::R8G8B8A8_UNORM, data);
149 	return tex;
150 }
151 
Clear(bool deleteThem)152 void FragmentTestCacheGLES::Clear(bool deleteThem) {
153 	if (deleteThem) {
154 		for (auto tex = cache_.begin(); tex != cache_.end(); ++tex) {
155 			render_->DeleteTexture(tex->second.texture);
156 		}
157 	}
158 	cache_.clear();
159 	lastTexture_ = 0;
160 }
161 
Decimate()162 void FragmentTestCacheGLES::Decimate() {
163 	if (--decimationCounter_ <= 0) {
164 		for (auto tex = cache_.begin(); tex != cache_.end(); ) {
165 			if (tex->second.lastFrame + FRAGTEST_TEXTURE_OLD_AGE < gpuStats.numFlips) {
166 				render_->DeleteTexture(tex->second.texture);
167 				cache_.erase(tex++);
168 			} else {
169 				++tex;
170 			}
171 		}
172 
173 		decimationCounter_ = FRAGTEST_DECIMATION_INTERVAL;
174 	}
175 
176 	lastTexture_ = 0;
177 }
178