/src/ogre/OgreMain/src/OgreRenderSystem.cpp
Line | Count | Source |
1 | | /* |
2 | | ----------------------------------------------------------------------------- |
3 | | This source file is part of OGRE |
4 | | (Object-oriented Graphics Rendering Engine) |
5 | | For the latest info, see http://www.ogre3d.org/ |
6 | | |
7 | | Copyright (c) 2000-2014 Torus Knot Software Ltd |
8 | | |
9 | | Permission is hereby granted, free of charge, to any person obtaining a copy |
10 | | of this software and associated documentation files (the "Software"), to deal |
11 | | in the Software without restriction, including without limitation the rights |
12 | | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
13 | | copies of the Software, and to permit persons to whom the Software is |
14 | | furnished to do so, subject to the following conditions: |
15 | | |
16 | | The above copyright notice and this permission notice shall be included in |
17 | | all copies or substantial portions of the Software. |
18 | | |
19 | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
20 | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
22 | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
23 | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
24 | | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
25 | | THE SOFTWARE. |
26 | | ----------------------------------------------------------------------------- |
27 | | */ |
28 | | #include "OgrePrerequisites.h" |
29 | | #include "OgreStableHeaders.h" |
30 | | // RenderSystem implementation |
31 | | // Note that most of this class is abstract since |
32 | | // we cannot know how to implement the behaviour without |
33 | | // being aware of the 3D API. However there are a few |
34 | | // simple functions which can have a base implementation |
35 | | |
36 | | #include "OgreHardwareOcclusionQuery.h" |
37 | | #include "OgreComponents.h" |
38 | | |
39 | | #ifdef OGRE_BUILD_COMPONENT_RTSHADERSYSTEM |
40 | | #include "OgreRTShaderConfig.h" |
41 | | #endif |
42 | | |
43 | | namespace Ogre { |
44 | | |
45 | | RenderSystem::Listener* RenderSystem::msSharedEventListener = 0; |
46 | | |
47 | | static const TexturePtr sNullTexPtr; |
48 | | |
49 | | //----------------------------------------------------------------------- |
50 | | RenderSystem::RenderSystem() |
51 | 0 | : mActiveRenderTarget(0) |
52 | 0 | , mTextureManager(0) |
53 | 0 | , mActiveViewport(0) |
54 | | // This means CULL clockwise vertices, i.e. front of poly is counter-clockwise |
55 | | // This makes it the same as OpenGL and other right-handed systems |
56 | 0 | , mCullingMode(CULL_CLOCKWISE) |
57 | 0 | , mBatchCount(0) |
58 | 0 | , mFaceCount(0) |
59 | 0 | , mVertexCount(0) |
60 | 0 | , mInvertVertexWinding(false) |
61 | 0 | , mIsReverseDepthBufferEnabled(false) |
62 | 0 | , mDisabledTexUnitsFrom(0) |
63 | 0 | , mCurrentPassIterationCount(0) |
64 | 0 | , mCurrentPassIterationNum(0) |
65 | 0 | , mDerivedDepthBias(false) |
66 | 0 | , mDerivedDepthBiasBase(0.0f) |
67 | 0 | , mDerivedDepthBiasMultiplier(0.0f) |
68 | 0 | , mDerivedDepthBiasSlopeScale(0.0f) |
69 | 0 | , mClipPlanesDirty(true) |
70 | 0 | , mRealCapabilities(0) |
71 | 0 | , mCurrentCapabilities(0) |
72 | 0 | , mUseCustomCapabilities(false) |
73 | 0 | , mNativeShadingLanguageVersion(0) |
74 | 0 | , mTexProjRelative(false) |
75 | 0 | , mTexProjRelativeOrigin(Vector3::ZERO) |
76 | 0 | { |
77 | 0 | mEventNames.push_back("RenderSystemCapabilitiesCreated"); |
78 | 0 | } |
79 | | |
80 | | void RenderSystem::initFixedFunctionParams() |
81 | 0 | { |
82 | 0 | if(mFixedFunctionParams) |
83 | 0 | return; |
84 | | |
85 | 0 | GpuLogicalBufferStructPtr logicalBufferStruct(new GpuLogicalBufferStruct()); |
86 | 0 | mFixedFunctionParams.reset(new GpuProgramParameters); |
87 | 0 | mFixedFunctionParams->_setLogicalIndexes(logicalBufferStruct); |
88 | 0 | mFixedFunctionParams->setAutoConstant(0, GpuProgramParameters::ACT_WORLD_MATRIX); |
89 | 0 | mFixedFunctionParams->setAutoConstant(4, GpuProgramParameters::ACT_VIEW_MATRIX); |
90 | 0 | mFixedFunctionParams->setAutoConstant(8, GpuProgramParameters::ACT_PROJECTION_MATRIX); |
91 | 0 | mFixedFunctionParams->setAutoConstant(12, GpuProgramParameters::ACT_SURFACE_AMBIENT_COLOUR); |
92 | 0 | mFixedFunctionParams->setAutoConstant(13, GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); |
93 | 0 | mFixedFunctionParams->setAutoConstant(14, GpuProgramParameters::ACT_SURFACE_SPECULAR_COLOUR); |
94 | 0 | mFixedFunctionParams->setAutoConstant(15, GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); |
95 | 0 | mFixedFunctionParams->setAutoConstant(16, GpuProgramParameters::ACT_SURFACE_SHININESS); |
96 | 0 | mFixedFunctionParams->setAutoConstant(17, GpuProgramParameters::ACT_POINT_PARAMS); |
97 | 0 | mFixedFunctionParams->setConstant(18, Vector4::ZERO); // ACT_FOG_PARAMS |
98 | 0 | mFixedFunctionParams->setConstant(19, Vector4::ZERO); // ACT_FOG_COLOUR |
99 | 0 | mFixedFunctionParams->setAutoConstant(20, GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); |
100 | | |
101 | | // allocate per light parameters. slots 21..69 |
102 | 0 | for(int i = 0; i < OGRE_MAX_SIMULTANEOUS_LIGHTS; i++) |
103 | 0 | { |
104 | 0 | size_t light_offset = 21 + i * 6; |
105 | 0 | mFixedFunctionParams->setConstant(light_offset + 0, Vector4::ZERO); // position |
106 | 0 | mFixedFunctionParams->setConstant(light_offset + 1, Vector4::ZERO); // direction |
107 | 0 | mFixedFunctionParams->setConstant(light_offset + 2, Vector4::ZERO); // diffuse |
108 | 0 | mFixedFunctionParams->setConstant(light_offset + 3, Vector4::ZERO); // specular |
109 | 0 | mFixedFunctionParams->setConstant(light_offset + 4, Vector4::ZERO); // attenuation |
110 | 0 | mFixedFunctionParams->setConstant(light_offset + 5, Vector4::ZERO); // spotlight |
111 | 0 | } |
112 | 0 | } |
113 | | |
114 | | void RenderSystem::setFFPLightParams(uint32 index, bool enabled) |
115 | 0 | { |
116 | 0 | if(!mFixedFunctionParams) |
117 | 0 | return; |
118 | | |
119 | 0 | size_t light_offset = 21 + 6 * index; |
120 | 0 | if (!enabled) |
121 | 0 | { |
122 | 0 | mFixedFunctionParams->clearAutoConstant(light_offset + 0); |
123 | 0 | mFixedFunctionParams->clearAutoConstant(light_offset + 1); |
124 | 0 | mFixedFunctionParams->clearAutoConstant(light_offset + 2); |
125 | 0 | mFixedFunctionParams->clearAutoConstant(light_offset + 3); |
126 | 0 | mFixedFunctionParams->clearAutoConstant(light_offset + 4); |
127 | 0 | mFixedFunctionParams->clearAutoConstant(light_offset + 5); |
128 | 0 | return; |
129 | 0 | } |
130 | 0 | mFixedFunctionParams->setAutoConstant(light_offset + 0, GpuProgramParameters::ACT_LIGHT_POSITION, index); |
131 | 0 | mFixedFunctionParams->setAutoConstant(light_offset + 1, GpuProgramParameters::ACT_LIGHT_DIRECTION, index); |
132 | 0 | mFixedFunctionParams->setAutoConstant(light_offset + 2, GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, index); |
133 | 0 | mFixedFunctionParams->setAutoConstant(light_offset + 3, GpuProgramParameters::ACT_LIGHT_SPECULAR_COLOUR, index); |
134 | 0 | mFixedFunctionParams->setAutoConstant(light_offset + 4, GpuProgramParameters::ACT_LIGHT_ATTENUATION, index); |
135 | 0 | mFixedFunctionParams->setAutoConstant(light_offset + 5, GpuProgramParameters::ACT_SPOTLIGHT_PARAMS, index); |
136 | 0 | } |
137 | | |
138 | | const HardwareBufferPtr& RenderSystem::updateDefaultUniformBuffer(GpuProgramType gptype, const ConstantList& params) |
139 | 0 | { |
140 | 0 | auto& ubo = mUniformBuffer[gptype]; |
141 | 0 | if (!ubo || ubo->getSizeInBytes() < params.size()) |
142 | 0 | { |
143 | 0 | ubo = HardwareBufferManager::getSingleton().createUniformBuffer(params.size()); |
144 | 0 | } |
145 | |
|
146 | 0 | ubo->writeData(0, params.size(), params.data(), true); |
147 | |
|
148 | 0 | return ubo; |
149 | 0 | } |
150 | | //----------------------------------------------------------------------- |
151 | | RenderSystem::~RenderSystem() |
152 | 0 | { |
153 | 0 | shutdown(); |
154 | 0 | OGRE_DELETE mRealCapabilities; |
155 | 0 | mRealCapabilities = 0; |
156 | | // Current capabilities managed externally |
157 | 0 | mCurrentCapabilities = 0; |
158 | 0 | } |
159 | | |
160 | | RenderWindowDescription RenderSystem::getRenderWindowDescription() const |
161 | 0 | { |
162 | 0 | RenderWindowDescription ret; |
163 | 0 | auto& miscParams = ret.miscParams; |
164 | |
|
165 | 0 | auto end = mOptions.end(); |
166 | |
|
167 | 0 | auto opt = mOptions.find("Full Screen"); |
168 | 0 | if (opt == end) |
169 | 0 | OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Can't find 'Full Screen' option"); |
170 | | |
171 | 0 | ret.useFullScreen = StringConverter::parseBool(opt->second.currentValue); |
172 | |
|
173 | 0 | opt = mOptions.find("Video Mode"); |
174 | 0 | if (opt == end) |
175 | 0 | OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Can't find 'Video Mode' option"); |
176 | | |
177 | 0 | StringStream mode(opt->second.currentValue); |
178 | 0 | String token; |
179 | |
|
180 | 0 | mode >> ret.width; |
181 | 0 | mode >> token; // 'x' as seperator between width and height |
182 | 0 | mode >> ret.height; |
183 | | |
184 | | // backend specific options. Presence determined by getConfigOptions |
185 | 0 | mode >> token; // '@' as seperator between bpp on D3D |
186 | 0 | if(!mode.eof()) |
187 | 0 | { |
188 | 0 | uint32 bpp; |
189 | 0 | mode >> bpp; |
190 | 0 | miscParams.emplace("colourDepth", std::to_string(bpp)); |
191 | 0 | } |
192 | |
|
193 | 0 | if((opt = mOptions.find("FSAA")) != end) |
194 | 0 | { |
195 | 0 | StringStream fsaaMode(opt->second.currentValue); |
196 | 0 | uint32_t fsaa; |
197 | 0 | fsaaMode >> fsaa; |
198 | 0 | miscParams.emplace("FSAA", std::to_string(fsaa)); |
199 | | |
200 | | // D3D specific |
201 | 0 | if(!fsaaMode.eof()) |
202 | 0 | { |
203 | 0 | String hint; |
204 | 0 | fsaaMode >> hint; |
205 | 0 | miscParams.emplace("FSAAHint", hint); |
206 | 0 | } |
207 | 0 | } |
208 | |
|
209 | 0 | if((opt = mOptions.find("VSync")) != end) |
210 | 0 | miscParams.emplace("vsync", opt->second.currentValue); |
211 | |
|
212 | 0 | if((opt = mOptions.find("sRGB Gamma Conversion")) != end) |
213 | 0 | miscParams.emplace("gamma", opt->second.currentValue); |
214 | |
|
215 | 0 | if((opt = mOptions.find("HDR Display")) != end) |
216 | 0 | miscParams.emplace("hdrDisplay", opt->second.currentValue); |
217 | |
|
218 | 0 | if((opt = mOptions.find("Colour Depth")) != end) |
219 | 0 | miscParams.emplace("colourDepth", opt->second.currentValue); |
220 | |
|
221 | 0 | if((opt = mOptions.find("VSync Interval")) != end) |
222 | 0 | miscParams.emplace("vsyncInterval", opt->second.currentValue); |
223 | |
|
224 | 0 | if((opt = mOptions.find("Display Frequency")) != end) |
225 | 0 | miscParams.emplace("displayFrequency", opt->second.currentValue); |
226 | |
|
227 | 0 | if((opt = mOptions.find("Content Scaling Factor")) != end) |
228 | 0 | miscParams["contentScalingFactor"] = opt->second.currentValue; |
229 | |
|
230 | 0 | if((opt = mOptions.find("Rendering Device")) != end) |
231 | 0 | { |
232 | | // try to parse "Monitor-NN-" |
233 | 0 | auto start = opt->second.currentValue.find('-') + 1; |
234 | 0 | auto len = opt->second.currentValue.find('-', start) - start; |
235 | 0 | if(start != String::npos) |
236 | 0 | miscParams["monitorIndex"] = opt->second.currentValue.substr(start, len); |
237 | 0 | } |
238 | |
|
239 | | #if OGRE_NO_QUAD_BUFFER_STEREO == 0 |
240 | | if((opt = mOptions.find("Frame Sequential Stereo")) != end) |
241 | | miscParams["stereoMode"] = opt->second.currentValue; |
242 | | #endif |
243 | 0 | return ret; |
244 | 0 | } |
245 | | |
246 | | //----------------------------------------------------------------------- |
247 | | void RenderSystem::_initRenderTargets(void) |
248 | 0 | { |
249 | | |
250 | | // Init stats |
251 | 0 | for (auto& rt : mRenderTargets) |
252 | 0 | { |
253 | 0 | rt.second->resetStatistics(); |
254 | 0 | } |
255 | 0 | } |
256 | | //----------------------------------------------------------------------- |
257 | | void RenderSystem::_updateAllRenderTargets(bool swapBuffers) |
258 | 0 | { |
259 | | // Update all in order of priority |
260 | | // This ensures render-to-texture targets get updated before render windows |
261 | 0 | for (auto& rt : mPrioritisedRenderTargets) |
262 | 0 | { |
263 | 0 | if (rt.second->isActive() && rt.second->isAutoUpdated()) |
264 | 0 | rt.second->update(swapBuffers); |
265 | 0 | } |
266 | 0 | } |
267 | | //----------------------------------------------------------------------- |
268 | | void RenderSystem::_swapAllRenderTargetBuffers() |
269 | 0 | { |
270 | 0 | OgreProfile("_swapAllRenderTargetBuffers"); |
271 | | // Update all in order of priority |
272 | | // This ensures render-to-texture targets get updated before render windows |
273 | 0 | for (auto& rt : mPrioritisedRenderTargets) |
274 | 0 | { |
275 | 0 | if (rt.second->isActive() && rt.second->isAutoUpdated()) |
276 | 0 | rt.second->swapBuffers(); |
277 | 0 | } |
278 | 0 | } |
279 | | //----------------------------------------------------------------------- |
280 | | void RenderSystem::_initialise() |
281 | 0 | { |
282 | | // Have I been registered by call to Root::setRenderSystem? |
283 | | /** Don't do this anymore, just allow via Root |
284 | | RenderSystem* regPtr = Root::getSingleton().getRenderSystem(); |
285 | | if (!regPtr || regPtr != this) |
286 | | // Register self - library user has come to me direct |
287 | | Root::getSingleton().setRenderSystem(this); |
288 | | */ |
289 | | |
290 | | |
291 | | // Subclasses should take it from here |
292 | | // They should ALL call this superclass method from |
293 | | // their own initialise() implementations. |
294 | | |
295 | 0 | mProgramBound.fill(false); |
296 | 0 | } |
297 | | |
298 | | //--------------------------------------------------------------------------------------------- |
299 | | void RenderSystem::useCustomRenderSystemCapabilities(RenderSystemCapabilities* capabilities) |
300 | 0 | { |
301 | 0 | if (mRealCapabilities) |
302 | 0 | { |
303 | 0 | OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, |
304 | 0 | "Custom render capabilities must be set before the RenderSystem is initialised"); |
305 | 0 | } |
306 | | |
307 | 0 | if (capabilities->getRenderSystemName() != getName()) |
308 | 0 | { |
309 | 0 | OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, |
310 | 0 | "Trying to use RenderSystemCapabilities that were created for a different RenderSystem"); |
311 | 0 | } |
312 | | |
313 | 0 | mCurrentCapabilities = capabilities; |
314 | 0 | mUseCustomCapabilities = true; |
315 | 0 | } |
316 | | |
317 | | //--------------------------------------------------------------------------------------------- |
318 | | RenderWindow* RenderSystem::_createRenderWindow(const String& name, unsigned int width, |
319 | | unsigned int height, bool fullScreen, |
320 | | const NameValuePairList* miscParams) |
321 | 0 | { |
322 | 0 | if (mRenderTargets.find(name) != mRenderTargets.end()) |
323 | 0 | { |
324 | 0 | OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Window with name '" + name + "' already exists"); |
325 | 0 | } |
326 | | |
327 | | // Log a message |
328 | 0 | StringStream ss; |
329 | 0 | ss << "RenderSystem::_createRenderWindow \"" << name << "\", " << |
330 | 0 | width << "x" << height << " "; |
331 | 0 | if (fullScreen) |
332 | 0 | ss << "fullscreen "; |
333 | 0 | else |
334 | 0 | ss << "windowed "; |
335 | |
|
336 | 0 | if (miscParams) |
337 | 0 | { |
338 | 0 | ss << " miscParams: "; |
339 | 0 | NameValuePairList::const_iterator it; |
340 | 0 | for (const auto& p : *miscParams) |
341 | 0 | { |
342 | 0 | ss << p.first << "=" << p.second << " "; |
343 | 0 | } |
344 | 0 | } |
345 | 0 | LogManager::getSingleton().logMessage(ss.str()); |
346 | |
|
347 | 0 | return NULL; |
348 | 0 | } |
349 | | //--------------------------------------------------------------------------------------------- |
350 | | void RenderSystem::destroyRenderWindow(const String& name) |
351 | 0 | { |
352 | 0 | destroyRenderTarget(name); |
353 | 0 | } |
354 | | //--------------------------------------------------------------------------------------------- |
355 | | void RenderSystem::destroyRenderTexture(const String& name) |
356 | 0 | { |
357 | 0 | destroyRenderTarget(name); |
358 | 0 | } |
359 | | //--------------------------------------------------------------------------------------------- |
360 | | void RenderSystem::destroyRenderTarget(const String& name) |
361 | 0 | { |
362 | 0 | RenderTarget* rt = detachRenderTarget(name); |
363 | 0 | OGRE_DELETE rt; |
364 | 0 | } |
365 | | //--------------------------------------------------------------------------------------------- |
366 | | void RenderSystem::attachRenderTarget( RenderTarget &target ) |
367 | 0 | { |
368 | 0 | assert( target.getPriority() < OGRE_NUM_RENDERTARGET_GROUPS ); |
369 | |
|
370 | 0 | mRenderTargets.emplace(target.getName(), &target); |
371 | 0 | mPrioritisedRenderTargets.emplace(target.getPriority(), &target); |
372 | 0 | } |
373 | | |
374 | | //--------------------------------------------------------------------------------------------- |
375 | | RenderTarget * RenderSystem::getRenderTarget( const String &name ) |
376 | 0 | { |
377 | 0 | RenderTargetMap::iterator it = mRenderTargets.find( name ); |
378 | 0 | RenderTarget *ret = NULL; |
379 | |
|
380 | 0 | if( it != mRenderTargets.end() ) |
381 | 0 | { |
382 | 0 | ret = it->second; |
383 | 0 | } |
384 | |
|
385 | 0 | return ret; |
386 | 0 | } |
387 | | |
388 | | //--------------------------------------------------------------------------------------------- |
389 | | RenderTarget * RenderSystem::detachRenderTarget( const String &name ) |
390 | 0 | { |
391 | 0 | RenderTargetMap::iterator it = mRenderTargets.find( name ); |
392 | 0 | RenderTarget *ret = NULL; |
393 | |
|
394 | 0 | if( it != mRenderTargets.end() ) |
395 | 0 | { |
396 | 0 | ret = it->second; |
397 | | |
398 | | /* Remove the render target from the priority groups. */ |
399 | 0 | RenderTargetPriorityMap::iterator itarg, itargend; |
400 | 0 | itargend = mPrioritisedRenderTargets.end(); |
401 | 0 | for( itarg = mPrioritisedRenderTargets.begin(); itarg != itargend; ++itarg ) |
402 | 0 | { |
403 | 0 | if( itarg->second == ret ) { |
404 | 0 | mPrioritisedRenderTargets.erase( itarg ); |
405 | 0 | break; |
406 | 0 | } |
407 | 0 | } |
408 | |
|
409 | 0 | mRenderTargets.erase( it ); |
410 | 0 | } |
411 | | /// If detached render target is the active render target, reset active render target |
412 | 0 | if(ret == mActiveRenderTarget) |
413 | 0 | mActiveRenderTarget = 0; |
414 | |
|
415 | 0 | return ret; |
416 | 0 | } |
417 | | //----------------------------------------------------------------------- |
418 | | Viewport* RenderSystem::_getViewport(void) |
419 | 0 | { |
420 | 0 | return mActiveViewport; |
421 | 0 | } |
422 | | //----------------------------------------------------------------------- |
423 | | void RenderSystem::_setTextureUnitSettings(size_t texUnit, TextureUnitState& tl) |
424 | 0 | { |
425 | 0 | if(texUnit >= getCapabilities()->getNumTextureUnits()) |
426 | 0 | return; |
427 | | |
428 | | // This method is only ever called to set a texture unit to valid details |
429 | | // The method _disableTextureUnit is called to turn a unit off |
430 | 0 | TexturePtr tex = tl._getTexturePtr(); |
431 | 0 | if(!tex || tl.isTextureLoadFailing()) |
432 | 0 | tex = mTextureManager->_getWarningTexture(); |
433 | |
|
434 | 0 | if(tl.getUnorderedAccessMipLevel() > -1) |
435 | 0 | { |
436 | 0 | tex->createShaderAccessPoint(texUnit, TA_READ_WRITE, tl.getUnorderedAccessMipLevel()); |
437 | 0 | return; |
438 | 0 | } |
439 | | |
440 | | // Bind texture (may be blank) |
441 | 0 | _setTexture(texUnit, true, tex); |
442 | |
|
443 | 0 | _setSampler(texUnit, *tl.getSampler()); |
444 | |
|
445 | 0 | if(!getCapabilities()->hasCapability(RSC_FIXED_FUNCTION)) |
446 | 0 | return; |
447 | | |
448 | | // Set texture coordinate set |
449 | 0 | _setTextureCoordSet(texUnit, tl.getTextureCoordSet()); |
450 | | |
451 | | // Set blend modes |
452 | | // Note, colour before alpha is important |
453 | 0 | _setTextureBlendMode(texUnit, tl.getColourBlendMode()); |
454 | 0 | _setTextureBlendMode(texUnit, tl.getAlphaBlendMode()); |
455 | |
|
456 | 0 | auto calcMode = tl._deriveTexCoordCalcMethod(); |
457 | 0 | if(calcMode == TEXCALC_PROJECTIVE_TEXTURE) |
458 | 0 | { |
459 | 0 | _setTextureCoordCalculation(texUnit, calcMode, tl.getProjectiveTexturingFrustum()); |
460 | 0 | } |
461 | 0 | else |
462 | 0 | { |
463 | 0 | _setTextureCoordCalculation(texUnit, calcMode); |
464 | 0 | } |
465 | | |
466 | | // Change tetxure matrix |
467 | 0 | _setTextureMatrix(texUnit, tl.getTextureTransform()); |
468 | 0 | } |
469 | | //----------------------------------------------------------------------- |
470 | | void RenderSystem::_disableTextureUnit(size_t texUnit) |
471 | 0 | { |
472 | 0 | _setTexture(texUnit, false, sNullTexPtr); |
473 | 0 | } |
474 | | //--------------------------------------------------------------------- |
475 | | void RenderSystem::_disableTextureUnitsFrom(size_t texUnit) |
476 | 0 | { |
477 | 0 | size_t disableTo = OGRE_MAX_TEXTURE_LAYERS; |
478 | 0 | if (disableTo > mDisabledTexUnitsFrom) |
479 | 0 | disableTo = mDisabledTexUnitsFrom; |
480 | 0 | mDisabledTexUnitsFrom = texUnit; |
481 | 0 | for (size_t i = texUnit; i < disableTo; ++i) |
482 | 0 | { |
483 | 0 | _disableTextureUnit(i); |
484 | 0 | } |
485 | 0 | } |
486 | | //--------------------------------------------------------------------- |
487 | | void RenderSystem::_cleanupDepthBuffers( bool bCleanManualBuffers ) |
488 | 0 | { |
489 | 0 | for (auto& m : mDepthBufferPool) |
490 | 0 | { |
491 | 0 | for (auto *b : m.second) |
492 | 0 | { |
493 | 0 | if (bCleanManualBuffers || !b->isManual()) |
494 | 0 | delete b; |
495 | 0 | } |
496 | 0 | m.second.clear(); |
497 | 0 | } |
498 | 0 | mDepthBufferPool.clear(); |
499 | 0 | } |
500 | | //----------------------------------------------------------------------- |
501 | | void RenderSystem::_beginFrame(void) |
502 | 0 | { |
503 | 0 | if (!mActiveViewport) |
504 | 0 | OGRE_EXCEPT(Exception::ERR_INVALID_STATE, "Cannot begin frame - no viewport selected."); |
505 | 0 | } |
506 | | //----------------------------------------------------------------------- |
507 | | CullingMode RenderSystem::_getCullingMode(void) const |
508 | 0 | { |
509 | 0 | return mCullingMode; |
510 | 0 | } |
511 | | //----------------------------------------------------------------------- |
512 | | void RenderSystem::setDepthBufferFor( RenderTarget *renderTarget ) |
513 | 0 | { |
514 | 0 | uint16 poolNum = renderTarget->getDepthBufferPool(); |
515 | 0 | if( poolNum == RBP_NONE ) |
516 | 0 | return; //RenderTarget explicitly requested no depth buffer |
517 | | |
518 | 0 | uint32 poolId = HashCombine(0, poolNum); |
519 | 0 | poolId = HashCombine(poolId, renderTarget->getFSAA()); |
520 | |
|
521 | 0 | if(!getCapabilities()->hasCapability(RSC_RTT_INDEPENDENT_BUFFER_SIZE)) |
522 | 0 | { |
523 | | //Depth buffer must be EXACT same size as RT |
524 | 0 | poolId = HashCombine(poolId, renderTarget->getWidth()); |
525 | 0 | poolId = HashCombine(poolId, renderTarget->getHeight()); |
526 | 0 | } |
527 | | |
528 | | //Find a depth buffer in the pool |
529 | 0 | bool bAttached = false; |
530 | 0 | for (auto& d : mDepthBufferPool[poolId]) { |
531 | 0 | bAttached = renderTarget->attachDepthBuffer(d); |
532 | 0 | if (bAttached) break; |
533 | 0 | } |
534 | | |
535 | | //Not found yet? Create a new one! |
536 | 0 | if( !bAttached ) |
537 | 0 | { |
538 | 0 | DepthBuffer *newDepthBuffer = _createDepthBufferFor( renderTarget ); |
539 | |
|
540 | 0 | if( newDepthBuffer ) |
541 | 0 | { |
542 | 0 | mDepthBufferPool[poolId].push_back( newDepthBuffer ); |
543 | |
|
544 | 0 | bAttached = renderTarget->attachDepthBuffer( newDepthBuffer ); |
545 | |
|
546 | 0 | OgreAssert( bAttached ,"A new DepthBuffer for a RenderTarget was created, but after creation" |
547 | 0 | " it says it's incompatible with that RT" ); |
548 | 0 | } |
549 | 0 | else |
550 | 0 | LogManager::getSingleton().logWarning( "Couldn't create a suited DepthBuffer" |
551 | 0 | "for RT: " + renderTarget->getName()); |
552 | 0 | } |
553 | 0 | } |
554 | | //----------------------------------------------------------------------- |
555 | | bool RenderSystem::isReverseDepthBufferEnabled() const |
556 | 0 | { |
557 | 0 | return mIsReverseDepthBufferEnabled; |
558 | 0 | } |
559 | | //----------------------------------------------------------------------- |
560 | | void RenderSystem::reinitialise() |
561 | 0 | { |
562 | 0 | shutdown(); |
563 | 0 | _initialise(); |
564 | 0 | } |
565 | | |
566 | | void RenderSystem::shutdown(void) |
567 | 0 | { |
568 | 0 | for (auto& q : mHwOcclusionQueries) |
569 | 0 | { |
570 | 0 | OGRE_DELETE q; |
571 | 0 | } |
572 | 0 | mHwOcclusionQueries.clear(); |
573 | |
|
574 | 0 | _cleanupDepthBuffers(); |
575 | | |
576 | | // Remove all the render targets. Destroy primary target last since others may depend on it. |
577 | | // Keep mRenderTargets valid all the time, so that render targets could receive |
578 | | // appropriate notifications, for example FBO based about GL context destruction. |
579 | 0 | RenderTarget* primary = 0; |
580 | 0 | for (RenderTargetMap::iterator it = mRenderTargets.begin(); it != mRenderTargets.end(); /* note - no increment */) |
581 | 0 | { |
582 | 0 | RenderTarget* current = it->second; |
583 | 0 | if (!primary && current->isPrimary()) |
584 | 0 | { |
585 | 0 | ++it; |
586 | 0 | primary = current; |
587 | 0 | } |
588 | 0 | else |
589 | 0 | { |
590 | 0 | it = mRenderTargets.erase(it); |
591 | 0 | OGRE_DELETE current; |
592 | 0 | } |
593 | 0 | } |
594 | 0 | OGRE_DELETE primary; |
595 | 0 | mRenderTargets.clear(); |
596 | |
|
597 | 0 | mPrioritisedRenderTargets.clear(); |
598 | 0 | } |
599 | | |
600 | | void RenderSystem::_setProjectionMatrix(Matrix4 m) |
601 | 0 | { |
602 | 0 | if (!mFixedFunctionParams) return; |
603 | | |
604 | 0 | if (mActiveRenderTarget->requiresTextureFlipping()) |
605 | 0 | { |
606 | | // Invert transformed y |
607 | 0 | m[1][0] = -m[1][0]; |
608 | 0 | m[1][1] = -m[1][1]; |
609 | 0 | m[1][2] = -m[1][2]; |
610 | 0 | m[1][3] = -m[1][3]; |
611 | 0 | } |
612 | |
|
613 | 0 | mFixedFunctionParams->setConstant(8, m); |
614 | 0 | applyFixedFunctionParams(mFixedFunctionParams, GPV_GLOBAL); |
615 | 0 | } |
616 | | //----------------------------------------------------------------------- |
617 | | void RenderSystem::_beginGeometryCount(void) |
618 | 0 | { |
619 | 0 | mBatchCount = mFaceCount = mVertexCount = 0; |
620 | 0 | } |
621 | | //----------------------------------------------------------------------- |
622 | | void RenderSystem::_render(const RenderOperation& op) |
623 | 0 | { |
624 | | // Update stats |
625 | 0 | size_t val; |
626 | |
|
627 | 0 | if (op.useIndexes) |
628 | 0 | val = op.indexData->indexCount; |
629 | 0 | else |
630 | 0 | val = op.vertexData->vertexCount; |
631 | |
|
632 | 0 | size_t trueInstanceNum = std::max<size_t>(op.numberOfInstances,1); |
633 | 0 | val *= trueInstanceNum; |
634 | | |
635 | | // account for a pass having multiple iterations |
636 | 0 | if (mCurrentPassIterationCount > 1) |
637 | 0 | val *= mCurrentPassIterationCount; |
638 | 0 | mCurrentPassIterationNum = 0; |
639 | |
|
640 | 0 | switch(op.operationType) |
641 | 0 | { |
642 | 0 | case RenderOperation::OT_TRIANGLE_LIST: |
643 | 0 | mFaceCount += (val / 3); |
644 | 0 | break; |
645 | 0 | case RenderOperation::OT_TRIANGLE_LIST_ADJ: |
646 | 0 | mFaceCount += (val / 6); |
647 | 0 | break; |
648 | 0 | case RenderOperation::OT_TRIANGLE_STRIP_ADJ: |
649 | 0 | mFaceCount += (val / 2 - 2); |
650 | 0 | break; |
651 | 0 | case RenderOperation::OT_TRIANGLE_STRIP: |
652 | 0 | case RenderOperation::OT_TRIANGLE_FAN: |
653 | 0 | mFaceCount += (val - 2); |
654 | 0 | break; |
655 | 0 | default: |
656 | 0 | break; |
657 | 0 | } |
658 | | |
659 | 0 | mVertexCount += op.vertexData->vertexCount * trueInstanceNum; |
660 | 0 | mBatchCount += mCurrentPassIterationCount; |
661 | | |
662 | | // sort out clip planes |
663 | | // have to do it here in case of matrix issues |
664 | 0 | if (mClipPlanesDirty) |
665 | 0 | { |
666 | 0 | setClipPlanesImpl(mClipPlanes); |
667 | 0 | mClipPlanesDirty = false; |
668 | 0 | } |
669 | 0 | } |
670 | | //----------------------------------------------------------------------- |
671 | | void RenderSystem::setInvertVertexWinding(bool invert) |
672 | 0 | { |
673 | 0 | mInvertVertexWinding = invert; |
674 | 0 | } |
675 | | //----------------------------------------------------------------------- |
676 | | bool RenderSystem::getInvertVertexWinding(void) const |
677 | 0 | { |
678 | 0 | return mInvertVertexWinding; |
679 | 0 | } |
680 | | //--------------------------------------------------------------------- |
681 | | void RenderSystem::setClipPlanes(const PlaneList& clipPlanes) |
682 | 0 | { |
683 | 0 | if (clipPlanes != mClipPlanes) |
684 | 0 | { |
685 | 0 | mClipPlanes = clipPlanes; |
686 | 0 | mClipPlanesDirty = true; |
687 | 0 | } |
688 | 0 | } |
689 | | //----------------------------------------------------------------------- |
690 | | void RenderSystem::_notifyCameraRemoved(const Camera* cam) |
691 | 0 | { |
692 | 0 | for (auto& rt : mRenderTargets) |
693 | 0 | { |
694 | 0 | auto target = rt.second; |
695 | 0 | target->_notifyCameraRemoved(cam); |
696 | 0 | } |
697 | 0 | } |
698 | | |
699 | | //--------------------------------------------------------------------- |
700 | | bool RenderSystem::updatePassIterationRenderState(void) |
701 | 0 | { |
702 | 0 | if (mCurrentPassIterationCount <= 1) |
703 | 0 | return false; |
704 | | |
705 | | // Update derived depth bias |
706 | 0 | if (mDerivedDepthBias) |
707 | 0 | { |
708 | 0 | _setDepthBias(mDerivedDepthBiasBase + mDerivedDepthBiasMultiplier * mCurrentPassIterationNum, |
709 | 0 | mDerivedDepthBiasSlopeScale); |
710 | 0 | } |
711 | |
|
712 | 0 | --mCurrentPassIterationCount; |
713 | 0 | ++mCurrentPassIterationNum; |
714 | |
|
715 | 0 | const uint16 mask = GPV_PASS_ITERATION_NUMBER; |
716 | |
|
717 | 0 | for (int i = 0; i < GPT_COUNT; i++) |
718 | 0 | { |
719 | 0 | if (!mActiveParameters[i]) |
720 | 0 | continue; |
721 | 0 | mActiveParameters[i]->incPassIterationNumber(); |
722 | 0 | bindGpuProgramParameters(GpuProgramType(i), mActiveParameters[i], mask); |
723 | 0 | } |
724 | |
|
725 | 0 | return true; |
726 | 0 | } |
727 | | |
728 | | //----------------------------------------------------------------------- |
729 | | void RenderSystem::setSharedListener(Listener* listener) |
730 | 0 | { |
731 | 0 | assert(msSharedEventListener == NULL || listener == NULL); // you can set or reset, but for safety not directly override |
732 | 0 | msSharedEventListener = listener; |
733 | 0 | } |
734 | | //----------------------------------------------------------------------- |
735 | | RenderSystem::Listener* RenderSystem::getSharedListener(void) |
736 | 0 | { |
737 | 0 | return msSharedEventListener; |
738 | 0 | } |
739 | | //----------------------------------------------------------------------- |
740 | | void RenderSystem::addListener(Listener* l) |
741 | 0 | { |
742 | 0 | mEventListeners.push_back(l); |
743 | 0 | } |
744 | | //----------------------------------------------------------------------- |
745 | | void RenderSystem::removeListener(Listener* l) |
746 | 0 | { |
747 | 0 | mEventListeners.remove(l); |
748 | 0 | } |
749 | | //----------------------------------------------------------------------- |
750 | | void RenderSystem::fireEvent(const String& name, const NameValuePairList* params) |
751 | 0 | { |
752 | 0 | for(auto& el : mEventListeners) |
753 | 0 | { |
754 | 0 | el->eventOccurred(name, params); |
755 | 0 | } |
756 | |
|
757 | 0 | if(msSharedEventListener) |
758 | 0 | msSharedEventListener->eventOccurred(name, params); |
759 | 0 | } |
760 | | //----------------------------------------------------------------------- |
761 | | void RenderSystem::destroyHardwareOcclusionQuery( HardwareOcclusionQuery *hq) |
762 | 0 | { |
763 | 0 | auto end = mHwOcclusionQueries.end(); |
764 | 0 | auto i = std::find(mHwOcclusionQueries.begin(), end, hq); |
765 | 0 | if (i != end) |
766 | 0 | { |
767 | 0 | mHwOcclusionQueries.erase(i); |
768 | 0 | OGRE_DELETE hq; |
769 | 0 | } |
770 | 0 | } |
771 | | //----------------------------------------------------------------------- |
772 | | void RenderSystem::bindGpuProgram(GpuProgram* prg) |
773 | 0 | { |
774 | 0 | auto gptype = prg->getType(); |
775 | | // mark clip planes dirty if changed (programmable can change space) |
776 | 0 | if(gptype == GPT_VERTEX_PROGRAM && !mClipPlanes.empty() && !mProgramBound[gptype]) |
777 | 0 | mClipPlanesDirty = true; |
778 | |
|
779 | 0 | mProgramBound[gptype] = true; |
780 | 0 | } |
781 | | //----------------------------------------------------------------------- |
782 | | void RenderSystem::unbindGpuProgram(GpuProgramType gptype) |
783 | 0 | { |
784 | | // mark clip planes dirty if changed (programmable can change space) |
785 | 0 | if(gptype == GPT_VERTEX_PROGRAM && !mClipPlanes.empty() && mProgramBound[gptype]) |
786 | 0 | mClipPlanesDirty = true; |
787 | |
|
788 | 0 | mProgramBound[gptype] = false; |
789 | 0 | } |
790 | | //----------------------------------------------------------------------- |
791 | | bool RenderSystem::isGpuProgramBound(GpuProgramType gptype) |
792 | 0 | { |
793 | 0 | return mProgramBound[gptype]; |
794 | 0 | } |
795 | | //--------------------------------------------------------------------- |
796 | | void RenderSystem::_setTextureProjectionRelativeTo(bool enabled, const Vector3& pos) |
797 | 0 | { |
798 | 0 | mTexProjRelative = enabled; |
799 | 0 | mTexProjRelativeOrigin = pos; |
800 | |
|
801 | 0 | } |
802 | | //--------------------------------------------------------------------- |
803 | | const String& RenderSystem::_getDefaultViewportMaterialScheme( void ) const |
804 | 0 | { |
805 | 0 | #ifdef RTSHADER_SYSTEM_BUILD_CORE_SHADERS |
806 | 0 | if (!getCapabilities()->hasCapability(RSC_FIXED_FUNCTION)) |
807 | 0 | { |
808 | 0 | return MSN_SHADERGEN; |
809 | 0 | } |
810 | 0 | #endif |
811 | 0 | return MSN_DEFAULT; |
812 | 0 | } |
813 | | //--------------------------------------------------------------------- |
814 | | void RenderSystem::setGlobalInstanceVertexBuffer(const HardwareVertexBufferSharedPtr& val) |
815 | 0 | { |
816 | 0 | OgreAssert(!val || val->isInstanceData(), "not an instance buffer"); |
817 | 0 | mSchemeInstancingData[_getDefaultViewportMaterialScheme()].vertexBuffer = val; |
818 | 0 | } |
819 | | void RenderSystem::enableSchemeInstancing(const String& materialScheme, const HardwareVertexBufferPtr& buffer, |
820 | | VertexDeclaration* decl, uint32 instanceCount) |
821 | 0 | { |
822 | 0 | OgreAssert(!buffer || buffer->isInstanceData(), "not an instance buffer"); |
823 | 0 | OgreAssert(decl, "null vertex declaration"); |
824 | 0 | OgreAssert(instanceCount > 1, "instance count must be greater than 1"); |
825 | 0 | mSchemeInstancingData.emplace(materialScheme, GlobalInstancingData{buffer, decl, instanceCount}); |
826 | 0 | } |
827 | | |
828 | | //--------------------------------------------------------------------- |
829 | | void RenderSystem::getCustomAttribute(const String& name, void* pData) |
830 | 0 | { |
831 | 0 | OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Attribute not found.", "RenderSystem::getCustomAttribute"); |
832 | 0 | } |
833 | | |
834 | | void RenderSystem::initConfigOptions() |
835 | 0 | { |
836 | | // FS setting possibilities |
837 | 0 | ConfigOption optFullScreen; |
838 | 0 | optFullScreen.name = "Full Screen"; |
839 | 0 | optFullScreen.possibleValues.push_back( "No" ); |
840 | 0 | optFullScreen.possibleValues.push_back( "Yes" ); |
841 | 0 | optFullScreen.currentValue = optFullScreen.possibleValues[0]; |
842 | 0 | optFullScreen.immutable = false; |
843 | 0 | mOptions[optFullScreen.name] = optFullScreen; |
844 | | |
845 | | // Video mode possibilities, can be overwritten by actual values |
846 | 0 | ConfigOption optVideoMode; |
847 | 0 | optVideoMode.name = "Video Mode"; |
848 | 0 | optVideoMode.possibleValues.push_back("1920 x 1080"); |
849 | 0 | optVideoMode.possibleValues.push_back("1280 x 720"); |
850 | 0 | optVideoMode.possibleValues.push_back("800 x 600"); |
851 | 0 | optVideoMode.currentValue = optVideoMode.possibleValues.back(); |
852 | 0 | optVideoMode.immutable = false; |
853 | 0 | mOptions[optVideoMode.name] = optVideoMode; |
854 | |
|
855 | 0 | ConfigOption optVSync; |
856 | 0 | optVSync.name = "VSync"; |
857 | 0 | optVSync.immutable = false; |
858 | 0 | optVSync.possibleValues.push_back("No"); |
859 | 0 | optVSync.possibleValues.push_back("Yes"); |
860 | 0 | optVSync.currentValue = optVSync.possibleValues[1]; |
861 | 0 | mOptions[optVSync.name] = optVSync; |
862 | |
|
863 | 0 | ConfigOption optVSyncInterval; |
864 | 0 | optVSyncInterval.name = "VSync Interval"; |
865 | 0 | optVSyncInterval.immutable = false; |
866 | 0 | optVSyncInterval.possibleValues.push_back("1"); |
867 | 0 | optVSyncInterval.possibleValues.push_back("2"); |
868 | 0 | optVSyncInterval.possibleValues.push_back("3"); |
869 | 0 | optVSyncInterval.possibleValues.push_back("4"); |
870 | 0 | optVSyncInterval.currentValue = optVSyncInterval.possibleValues[0]; |
871 | 0 | mOptions[optVSyncInterval.name] = optVSyncInterval; |
872 | |
|
873 | 0 | ConfigOption optSRGB; |
874 | 0 | optSRGB.name = "sRGB Gamma Conversion"; |
875 | 0 | optSRGB.immutable = false; |
876 | 0 | optSRGB.possibleValues.push_back("No"); |
877 | 0 | optSRGB.possibleValues.push_back("Yes"); |
878 | 0 | optSRGB.currentValue = optSRGB.possibleValues[0]; |
879 | 0 | mOptions[optSRGB.name] = optSRGB; |
880 | |
|
881 | 0 | ConfigOption optHDRDisplay; |
882 | 0 | optHDRDisplay.name = "HDR Display"; |
883 | 0 | optHDRDisplay.immutable = false; |
884 | 0 | optHDRDisplay.possibleValues.push_back("No"); |
885 | 0 | optHDRDisplay.possibleValues.push_back("Yes"); |
886 | 0 | optHDRDisplay.currentValue = optHDRDisplay.possibleValues[0]; |
887 | 0 | mOptions[optHDRDisplay.name] = optHDRDisplay; |
888 | |
|
889 | | #if OGRE_NO_QUAD_BUFFER_STEREO == 0 |
890 | | ConfigOption optStereoMode; |
891 | | optStereoMode.name = "Frame Sequential Stereo"; |
892 | | optStereoMode.possibleValues.push_back("Off"); |
893 | | optStereoMode.possibleValues.push_back("On"); |
894 | | optStereoMode.currentValue = optStereoMode.possibleValues[0]; |
895 | | optStereoMode.immutable = false; |
896 | | |
897 | | mOptions[optStereoMode.name] = optStereoMode; |
898 | | #endif |
899 | 0 | } |
900 | | |
901 | | CompareFunction RenderSystem::reverseCompareFunction(CompareFunction func) |
902 | 0 | { |
903 | 0 | switch(func) |
904 | 0 | { |
905 | 0 | default: |
906 | 0 | return func; |
907 | 0 | case CMPF_LESS: |
908 | 0 | return CMPF_GREATER; |
909 | 0 | case CMPF_LESS_EQUAL: |
910 | 0 | return CMPF_GREATER_EQUAL; |
911 | 0 | case CMPF_GREATER_EQUAL: |
912 | 0 | return CMPF_LESS_EQUAL; |
913 | 0 | case CMPF_GREATER: |
914 | 0 | return CMPF_LESS; |
915 | 0 | } |
916 | 0 | } |
917 | | |
918 | | bool RenderSystem::flipFrontFace() const |
919 | 0 | { |
920 | 0 | return mInvertVertexWinding != mActiveRenderTarget->requiresTextureFlipping(); |
921 | 0 | } |
922 | | |
923 | | void RenderSystem::setStencilCheckEnabled(bool enabled) |
924 | 0 | { |
925 | 0 | mStencilState.enabled = enabled; |
926 | 0 | if (!enabled) |
927 | 0 | setStencilState(mStencilState); |
928 | 0 | } |
929 | | void RenderSystem::setStencilBufferParams(CompareFunction func, uint32 refValue, uint32 compareMask, |
930 | | uint32 writeMask, StencilOperation stencilFailOp, |
931 | | StencilOperation depthFailOp, StencilOperation passOp, |
932 | | bool twoSidedOperation) |
933 | 0 | { |
934 | 0 | mStencilState.compareOp = func; |
935 | 0 | mStencilState.referenceValue = refValue; |
936 | 0 | mStencilState.compareMask = compareMask; |
937 | 0 | mStencilState.writeMask = writeMask; |
938 | 0 | mStencilState.stencilFailOp = stencilFailOp; |
939 | 0 | mStencilState.depthFailOp = depthFailOp; |
940 | 0 | mStencilState.depthStencilPassOp = passOp; |
941 | 0 | mStencilState.twoSidedOperation = twoSidedOperation; |
942 | 0 | if(mStencilState.enabled) |
943 | 0 | setStencilState(mStencilState); |
944 | 0 | } |
945 | | } |
946 | | |