// Copyright 2016 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "Renderer.hpp" #include "Clipper.hpp" #include "Surface.hpp" #include "Primitive.hpp" #include "Polygon.hpp" #include "Main/FrameBuffer.hpp" #include "Main/SwiftConfig.hpp" #include "Reactor/Reactor.hpp" #include "Shader/Constants.hpp" #include "Common/MutexLock.hpp" #include "Common/CPUID.hpp" #include "Common/Memory.hpp" #include "Common/Resource.hpp" #include "Common/Half.hpp" #include "Common/Math.hpp" #include "Common/Timer.hpp" #include "Common/Debug.hpp" #undef max bool disableServer = true; #ifndef NDEBUG unsigned int minPrimitives = 1; unsigned int maxPrimitives = 1 << 21; #endif namespace sw { extern bool halfIntegerCoordinates; // Pixel centers are not at integer coordinates extern bool symmetricNormalizedDepth; // [-1, 1] instead of [0, 1] extern bool booleanFaceRegister; extern bool fullPixelPositionRegister; extern bool leadingVertexFirst; // Flat shading uses first vertex, else last extern bool secondaryColor; // Specular lighting is applied after texturing extern bool colorsDefaultToZero; extern bool forceWindowed; extern bool complementaryDepthBuffer; extern bool postBlendSRGB; extern bool exactColorRounding; extern TransparencyAntialiasing transparencyAntialiasing; extern bool forceClearRegisters; extern bool precacheVertex; extern bool precacheSetup; extern bool precachePixel; static const int batchSize = 128; AtomicInt threadCount(1); AtomicInt Renderer::unitCount(1); AtomicInt Renderer::clusterCount(1); TranscendentalPrecision logPrecision = ACCURATE; TranscendentalPrecision expPrecision = ACCURATE; TranscendentalPrecision rcpPrecision = ACCURATE; TranscendentalPrecision rsqPrecision = ACCURATE; bool perspectiveCorrection = true; static void setGlobalRenderingSettings(Conventions conventions, bool exactColorRounding) { static bool initialized = false; if(!initialized) { sw::halfIntegerCoordinates = conventions.halfIntegerCoordinates; sw::symmetricNormalizedDepth = conventions.symmetricNormalizedDepth; sw::booleanFaceRegister = conventions.booleanFaceRegister; sw::fullPixelPositionRegister = conventions.fullPixelPositionRegister; sw::leadingVertexFirst = conventions.leadingVertexFirst; sw::secondaryColor = conventions.secondaryColor; sw::colorsDefaultToZero = conventions.colorsDefaultToZero; sw::exactColorRounding = exactColorRounding; initialized = true; } } struct Parameters { Renderer *renderer; int threadIndex; }; Query::Query(Type type) : building(false), data(0), type(type), reference(1) { } void Query::addRef() { ++reference; // Atomic } void Query::release() { int ref = reference--; // Atomic ASSERT(ref >= 0); if(ref == 0) { delete this; } } DrawCall::DrawCall() { queries = 0; vsDirtyConstF = VERTEX_UNIFORM_VECTORS + 1; vsDirtyConstI = 16; vsDirtyConstB = 16; psDirtyConstF = FRAGMENT_UNIFORM_VECTORS; psDirtyConstI = 16; psDirtyConstB = 16; references = -1; data = (DrawData*)allocate(sizeof(DrawData)); data->constants = &constants; } DrawCall::~DrawCall() { delete queries; deallocate(data); } Renderer::Renderer(Context *context, Conventions conventions, bool exactColorRounding) : VertexProcessor(context), PixelProcessor(context), SetupProcessor(context), context(context), viewport() { setGlobalRenderingSettings(conventions, exactColorRounding); setRenderTarget(0, 0); clipper = new Clipper(symmetricNormalizedDepth); blitter = new Blitter; updateViewMatrix = true; updateBaseMatrix = true; updateProjectionMatrix = true; updateClipPlanes = true; #if PERF_HUD resetTimers(); #endif for(int i = 0; i < 16; i++) { vertexTask[i] = 0; worker[i] = 0; resume[i] = 0; suspend[i] = 0; } threadsAwake = 0; resumeApp = new Event(); currentDraw = 0; nextDraw = 0; qHead = 0; qSize = 0; for(int i = 0; i < 16; i++) { triangleBatch[i] = 0; primitiveBatch[i] = 0; } for(int draw = 0; draw < DRAW_COUNT; draw++) { drawCall[draw] = new DrawCall(); drawList[draw] = drawCall[draw]; } for(int unit = 0; unit < 16; unit++) { primitiveProgress[unit].init(); } for(int cluster = 0; cluster < 16; cluster++) { pixelProgress[cluster].init(); } clipFlags = 0; swiftConfig = new SwiftConfig(disableServer); updateConfiguration(true); sync = new Resource(0); } Renderer::~Renderer() { sync->lock(EXCLUSIVE); sync->destruct(); terminateThreads(); sync->unlock(); delete clipper; clipper = nullptr; delete blitter; blitter = nullptr; delete resumeApp; resumeApp = nullptr; for(int draw = 0; draw < DRAW_COUNT; draw++) { delete drawCall[draw]; drawCall[draw] = nullptr; } delete swiftConfig; swiftConfig = nullptr; } // This object has to be mem aligned void* Renderer::operator new(size_t size) { ASSERT(size == sizeof(Renderer)); // This operator can't be called from a derived class return sw::allocate(sizeof(Renderer), 16); } void Renderer::operator delete(void * mem) { sw::deallocate(mem); } void Renderer::draw(DrawType drawType, unsigned int indexOffset, unsigned int count, bool update) { #ifndef NDEBUG if(count < minPrimitives || count > maxPrimitives) { return; } #endif context->drawType = drawType; updateConfiguration(); updateClipper(); int ss = context->getSuperSampleCount(); int ms = context->getMultiSampleCount(); bool requiresSync = false; for(int q = 0; q < ss; q++) { unsigned int oldMultiSampleMask = context->multiSampleMask; context->multiSampleMask = (context->sampleMask >> (ms * q)) & ((unsigned)0xFFFFFFFF >> (32 - ms)); if(!context->multiSampleMask) { continue; } sync->lock(sw::PRIVATE); if(update || oldMultiSampleMask != context->multiSampleMask) { vertexState = VertexProcessor::update(drawType); setupState = SetupProcessor::update(); pixelState = PixelProcessor::update(); vertexRoutine = VertexProcessor::routine(vertexState); setupRoutine = SetupProcessor::routine(setupState); pixelRoutine = PixelProcessor::routine(pixelState); } int batch = batchSize / ms; int (Renderer::*setupPrimitives)(int batch, int count); if(context->isDrawTriangle()) { switch(context->fillMode) { case FILL_SOLID: setupPrimitives = &Renderer::setupSolidTriangles; break; case FILL_WIREFRAME: setupPrimitives = &Renderer::setupWireframeTriangle; batch = 1; break; case FILL_VERTEX: setupPrimitives = &Renderer::setupVertexTriangle; batch = 1; break; default: ASSERT(false); return; } } else if(context->isDrawLine()) { setupPrimitives = &Renderer::setupLines; } else // Point draw { setupPrimitives = &Renderer::setupPoints; } DrawCall *draw = nullptr; do { for(int i = 0; i < DRAW_COUNT; i++) { if(drawCall[i]->references == -1) { draw = drawCall[i]; drawList[nextDraw & DRAW_COUNT_BITS] = draw; break; } } if(!draw) { resumeApp->wait(); } } while(!draw); DrawData *data = draw->data; if(queries.size() != 0) { draw->queries = new std::list(); bool includePrimitivesWrittenQueries = vertexState.transformFeedbackQueryEnabled && vertexState.transformFeedbackEnabled; for(auto &query : queries) { if(includePrimitivesWrittenQueries || (query->type != Query::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN)) { query->addRef(); draw->queries->push_back(query); } } } draw->drawType = drawType; draw->batchSize = batch; draw->vertexRoutine = vertexRoutine; draw->setupRoutine = setupRoutine; draw->pixelRoutine = pixelRoutine; draw->vertexPointer = (VertexProcessor::RoutinePointer)vertexRoutine->getEntry(); draw->setupPointer = (SetupProcessor::RoutinePointer)setupRoutine->getEntry(); draw->pixelPointer = (PixelProcessor::RoutinePointer)pixelRoutine->getEntry(); draw->setupPrimitives = setupPrimitives; draw->setupState = setupState; for(int i = 0; i < MAX_VERTEX_INPUTS; i++) { draw->vertexStream[i] = context->input[i].resource; data->input[i] = context->input[i].buffer; data->stride[i] = context->input[i].stride; if(draw->vertexStream[i]) { draw->vertexStream[i]->lock(PUBLIC, PRIVATE); } } if(context->indexBuffer) { data->indices = (unsigned char*)context->indexBuffer->lock(PUBLIC, PRIVATE) + indexOffset; } draw->indexBuffer = context->indexBuffer; for(int sampler = 0; sampler < TOTAL_IMAGE_UNITS; sampler++) { draw->texture[sampler] = 0; } for(int sampler = 0; sampler < TEXTURE_IMAGE_UNITS; sampler++) { if(pixelState.sampler[sampler].textureType != TEXTURE_NULL) { draw->texture[sampler] = context->texture[sampler]; draw->texture[sampler]->lock(PUBLIC, isReadWriteTexture(sampler) ? MANAGED : PRIVATE); // If the texure is both read and written, use the same read/write lock as render targets data->mipmap[sampler] = context->sampler[sampler].getTextureData(); requiresSync |= context->sampler[sampler].requiresSync(); } } if(context->pixelShader) { if(draw->psDirtyConstF) { memcpy(&data->ps.cW, PixelProcessor::cW, sizeof(word4) * 4 * (draw->psDirtyConstF < 8 ? draw->psDirtyConstF : 8)); memcpy(&data->ps.c, PixelProcessor::c, sizeof(float4) * draw->psDirtyConstF); draw->psDirtyConstF = 0; } if(draw->psDirtyConstI) { memcpy(&data->ps.i, PixelProcessor::i, sizeof(int4) * draw->psDirtyConstI); draw->psDirtyConstI = 0; } if(draw->psDirtyConstB) { memcpy(&data->ps.b, PixelProcessor::b, sizeof(bool) * draw->psDirtyConstB); draw->psDirtyConstB = 0; } PixelProcessor::lockUniformBuffers(data->ps.u, draw->pUniformBuffers); } else { for(int i = 0; i < MAX_UNIFORM_BUFFER_BINDINGS; i++) { draw->pUniformBuffers[i] = nullptr; } } if(context->pixelShaderModel() <= 0x0104) { for(int stage = 0; stage < 8; stage++) { if(pixelState.textureStage[stage].stageOperation != TextureStage::STAGE_DISABLE || context->pixelShader) { data->textureStage[stage] = context->textureStage[stage].uniforms; } else break; } } if(context->vertexShader) { if(context->vertexShader->getShaderModel() >= 0x0300) { for(int sampler = 0; sampler < VERTEX_TEXTURE_IMAGE_UNITS; sampler++) { if(vertexState.sampler[sampler].textureType != TEXTURE_NULL) { draw->texture[TEXTURE_IMAGE_UNITS + sampler] = context->texture[TEXTURE_IMAGE_UNITS + sampler]; draw->texture[TEXTURE_IMAGE_UNITS + sampler]->lock(PUBLIC, PRIVATE); data->mipmap[TEXTURE_IMAGE_UNITS + sampler] = context->sampler[TEXTURE_IMAGE_UNITS + sampler].getTextureData(); requiresSync |= context->sampler[TEXTURE_IMAGE_UNITS + sampler].requiresSync(); } } } if(draw->vsDirtyConstF) { memcpy(&data->vs.c, VertexProcessor::c, sizeof(float4) * draw->vsDirtyConstF); draw->vsDirtyConstF = 0; } if(draw->vsDirtyConstI) { memcpy(&data->vs.i, VertexProcessor::i, sizeof(int4) * draw->vsDirtyConstI); draw->vsDirtyConstI = 0; } if(draw->vsDirtyConstB) { memcpy(&data->vs.b, VertexProcessor::b, sizeof(bool) * draw->vsDirtyConstB); draw->vsDirtyConstB = 0; } if(context->vertexShader->isInstanceIdDeclared()) { data->instanceID = context->instanceID; } VertexProcessor::lockUniformBuffers(data->vs.u, draw->vUniformBuffers); VertexProcessor::lockTransformFeedbackBuffers(data->vs.t, data->vs.reg, data->vs.row, data->vs.col, data->vs.str, draw->transformFeedbackBuffers); } else { data->ff = ff; draw->vsDirtyConstF = VERTEX_UNIFORM_VECTORS + 1; draw->vsDirtyConstI = 16; draw->vsDirtyConstB = 16; for(int i = 0; i < MAX_UNIFORM_BUFFER_BINDINGS; i++) { draw->vUniformBuffers[i] = nullptr; } for(int i = 0; i < MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS; i++) { draw->transformFeedbackBuffers[i] = nullptr; } } if(pixelState.stencilActive) { data->stencil[0] = stencil; data->stencil[1] = stencilCCW; } if(pixelState.fogActive) { data->fog = fog; } if(setupState.isDrawPoint) { data->point = point; } data->lineWidth = context->lineWidth; data->factor = factor; if(pixelState.transparencyAntialiasing == TRANSPARENCY_ALPHA_TO_COVERAGE) { float ref = context->alphaReference * (1.0f / 255.0f); float margin = sw::min(ref, 1.0f - ref); if(ms == 4) { data->a2c0 = replicate(ref - margin * 0.6f); data->a2c1 = replicate(ref - margin * 0.2f); data->a2c2 = replicate(ref + margin * 0.2f); data->a2c3 = replicate(ref + margin * 0.6f); } else if(ms == 2) { data->a2c0 = replicate(ref - margin * 0.3f); data->a2c1 = replicate(ref + margin * 0.3f); } else ASSERT(false); } if(pixelState.occlusionEnabled) { for(int cluster = 0; cluster < clusterCount; cluster++) { data->occlusion[cluster] = 0; } } #if PERF_PROFILE for(int cluster = 0; cluster < clusterCount; cluster++) { for(int i = 0; i < PERF_TIMERS; i++) { data->cycles[i][cluster] = 0; } } #endif // Viewport { float W = 0.5f * viewport.width; float H = 0.5f * viewport.height; float X0 = viewport.x0 + W; float Y0 = viewport.y0 + H; float N = viewport.minZ; float F = viewport.maxZ; float Z = F - N; if(context->isDrawTriangle(false)) { N += context->depthBias; } if(complementaryDepthBuffer) { Z = -Z; N = 1 - N; } static const float X[5][16] = // Fragment offsets { {+0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f}, // 1 sample {-0.2500f, +0.2500f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f}, // 2 samples {-0.3000f, +0.1000f, +0.3000f, -0.1000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f}, // 4 samples {+0.1875f, -0.3125f, +0.3125f, -0.4375f, -0.0625f, +0.4375f, +0.0625f, -0.1875f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f}, // 8 samples {+0.2553f, -0.1155f, +0.1661f, -0.1828f, +0.2293f, -0.4132f, -0.1773f, -0.0577f, +0.3891f, -0.4656f, +0.4103f, +0.4248f, -0.2109f, +0.3966f, -0.2664f, -0.3872f} // 16 samples }; static const float Y[5][16] = // Fragment offsets { {+0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f}, // 1 sample {-0.2500f, +0.2500f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f}, // 2 samples {-0.1000f, -0.3000f, +0.1000f, +0.3000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f}, // 4 samples {-0.4375f, -0.3125f, -0.1875f, -0.0625f, +0.0625f, +0.1875f, +0.3125f, +0.4375f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f, +0.0000f}, // 8 samples {-0.4503f, +0.1883f, +0.3684f, -0.4668f, -0.0690f, -0.1315f, +0.4999f, +0.0728f, +0.1070f, -0.3086f, +0.3725f, -0.1547f, -0.1102f, -0.3588f, +0.1789f, +0.0269f} // 16 samples }; int s = sw::log2(ss); data->Wx16 = replicate(W * 16); data->Hx16 = replicate(H * 16); data->X0x16 = replicate(X0 * 16 - 8); data->Y0x16 = replicate(Y0 * 16 - 8); data->XXXX = replicate(X[s][q] / W); data->YYYY = replicate(Y[s][q] / H); data->halfPixelX = replicate(0.5f / W); data->halfPixelY = replicate(0.5f / H); data->viewportHeight = abs(viewport.height); data->slopeDepthBias = context->slopeDepthBias; data->depthRange = Z; data->depthNear = N; draw->clipFlags = clipFlags; if(clipFlags) { if(clipFlags & Clipper::CLIP_PLANE0) data->clipPlane[0] = clipPlane[0]; if(clipFlags & Clipper::CLIP_PLANE1) data->clipPlane[1] = clipPlane[1]; if(clipFlags & Clipper::CLIP_PLANE2) data->clipPlane[2] = clipPlane[2]; if(clipFlags & Clipper::CLIP_PLANE3) data->clipPlane[3] = clipPlane[3]; if(clipFlags & Clipper::CLIP_PLANE4) data->clipPlane[4] = clipPlane[4]; if(clipFlags & Clipper::CLIP_PLANE5) data->clipPlane[5] = clipPlane[5]; } } // Target { for(int index = 0; index < RENDERTARGETS; index++) { draw->renderTarget[index] = context->renderTarget[index]; if(draw->renderTarget[index]) { unsigned int layer = context->renderTargetLayer[index]; requiresSync |= context->renderTarget[index]->requiresSync(); data->colorBuffer[index] = (unsigned int*)context->renderTarget[index]->lockInternal(0, 0, layer, LOCK_READWRITE, MANAGED); data->colorBuffer[index] += q * ms * context->renderTarget[index]->getSliceB(true); data->colorPitchB[index] = context->renderTarget[index]->getInternalPitchB(); data->colorSliceB[index] = context->renderTarget[index]->getInternalSliceB(); } } draw->depthBuffer = context->depthBuffer; draw->stencilBuffer = context->stencilBuffer; if(draw->depthBuffer) { unsigned int layer = context->depthBufferLayer; requiresSync |= context->depthBuffer->requiresSync(); data->depthBuffer = (float*)context->depthBuffer->lockInternal(0, 0, layer, LOCK_READWRITE, MANAGED); data->depthBuffer += q * ms * context->depthBuffer->getSliceB(true); data->depthPitchB = context->depthBuffer->getInternalPitchB(); data->depthSliceB = context->depthBuffer->getInternalSliceB(); } if(draw->stencilBuffer) { unsigned int layer = context->stencilBufferLayer; requiresSync |= context->stencilBuffer->requiresSync(); data->stencilBuffer = (unsigned char*)context->stencilBuffer->lockStencil(0, 0, layer, MANAGED); data->stencilBuffer += q * ms * context->stencilBuffer->getSliceB(true); data->stencilPitchB = context->stencilBuffer->getStencilPitchB(); data->stencilSliceB = context->stencilBuffer->getStencilSliceB(); } } // Scissor { data->scissorX0 = scissor.x0; data->scissorX1 = scissor.x1; data->scissorY0 = scissor.y0; data->scissorY1 = scissor.y1; } draw->primitive = 0; draw->count = count; draw->references = (count + batch - 1) / batch; schedulerMutex.lock(); ++nextDraw; // Atomic schedulerMutex.unlock(); #ifndef NDEBUG if(threadCount == 1) // Use main thread for draw execution { threadsAwake = 1; task[0].type = Task::RESUME; taskLoop(0); } else #endif { if(!threadsAwake) { suspend[0]->wait(); threadsAwake = 1; task[0].type = Task::RESUME; resume[0]->signal(); } } } // TODO(sugoi): This is a temporary brute-force workaround to ensure IOSurface synchronization. if(requiresSync) { synchronize(); } } void Renderer::clear(void *value, Format format, Surface *dest, const Rect &clearRect, unsigned int rgbaMask) { blitter->clear(value, format, dest, clearRect, rgbaMask); } void Renderer::blit(Surface *source, const SliceRectF &sRect, Surface *dest, const SliceRect &dRect, bool filter, bool isStencil, bool sRGBconversion) { blitter->blit(source, sRect, dest, dRect, {filter, isStencil, sRGBconversion}); } void Renderer::blit3D(Surface *source, Surface *dest) { blitter->blit3D(source, dest); } void Renderer::threadFunction(void *parameters) { Renderer *renderer = static_cast(parameters)->renderer; int threadIndex = static_cast(parameters)->threadIndex; if(logPrecision < IEEE) { CPUID::setFlushToZero(true); CPUID::setDenormalsAreZero(true); } renderer->threadLoop(threadIndex); } void Renderer::threadLoop(int threadIndex) { while(!exitThreads) { taskLoop(threadIndex); suspend[threadIndex]->signal(); resume[threadIndex]->wait(); } } void Renderer::taskLoop(int threadIndex) { while(task[threadIndex].type != Task::SUSPEND) { scheduleTask(threadIndex); executeTask(threadIndex); } } void Renderer::findAvailableTasks() { // Find pixel tasks for(int cluster = 0; cluster < clusterCount; cluster++) { if(!pixelProgress[cluster].executing) { for(int unit = 0; unit < unitCount; unit++) { if(primitiveProgress[unit].references > 0) // Contains processed primitives { if(pixelProgress[cluster].drawCall == primitiveProgress[unit].drawCall) { if(pixelProgress[cluster].processedPrimitives == primitiveProgress[unit].firstPrimitive) // Previous primitives have been rendered { Task &task = taskQueue[qHead]; task.type = Task::PIXELS; task.primitiveUnit = unit; task.pixelCluster = cluster; pixelProgress[cluster].executing = true; // Commit to the task queue qHead = (qHead + 1) & TASK_COUNT_BITS; qSize++; break; } } } } } } // Find primitive tasks if(currentDraw == nextDraw) { return; // No more primitives to process } for(int unit = 0; unit < unitCount; unit++) { DrawCall *draw = drawList[currentDraw & DRAW_COUNT_BITS]; int primitive = draw->primitive; int count = draw->count; if(primitive >= count) { ++currentDraw; // Atomic if(currentDraw == nextDraw) { return; // No more primitives to process } draw = drawList[currentDraw & DRAW_COUNT_BITS]; } if(!primitiveProgress[unit].references) // Task not already being executed and not still in use by a pixel unit { primitive = draw->primitive; count = draw->count; int batch = draw->batchSize; primitiveProgress[unit].drawCall = currentDraw; primitiveProgress[unit].firstPrimitive = primitive; primitiveProgress[unit].primitiveCount = count - primitive >= batch ? batch : count - primitive; draw->primitive += batch; Task &task = taskQueue[qHead]; task.type = Task::PRIMITIVES; task.primitiveUnit = unit; primitiveProgress[unit].references = -1; // Commit to the task queue qHead = (qHead + 1) & TASK_COUNT_BITS; qSize++; } } } void Renderer::scheduleTask(int threadIndex) { schedulerMutex.lock(); int curThreadsAwake = threadsAwake; if((int)qSize < threadCount - curThreadsAwake + 1) { findAvailableTasks(); } if(qSize != 0) { task[threadIndex] = taskQueue[(qHead - qSize) & TASK_COUNT_BITS]; qSize--; if(curThreadsAwake != threadCount) { int wakeup = qSize - curThreadsAwake + 1; for(int i = 0; i < threadCount && wakeup > 0; i++) { if(task[i].type == Task::SUSPEND) { suspend[i]->wait(); task[i].type = Task::RESUME; resume[i]->signal(); ++threadsAwake; // Atomic wakeup--; } } } } else { task[threadIndex].type = Task::SUSPEND; --threadsAwake; // Atomic } schedulerMutex.unlock(); } void Renderer::executeTask(int threadIndex) { #if PERF_HUD int64_t startTick = Timer::ticks(); #endif switch(task[threadIndex].type) { case Task::PRIMITIVES: { int unit = task[threadIndex].primitiveUnit; int input = primitiveProgress[unit].firstPrimitive; int count = primitiveProgress[unit].primitiveCount; DrawCall *draw = drawList[primitiveProgress[unit].drawCall & DRAW_COUNT_BITS]; int (Renderer::*setupPrimitives)(int batch, int count) = draw->setupPrimitives; processPrimitiveVertices(unit, input, count, draw->count, threadIndex); #if PERF_HUD int64_t time = Timer::ticks(); vertexTime[threadIndex] += time - startTick; startTick = time; #endif int visible = 0; if(!draw->setupState.rasterizerDiscard) { visible = (this->*setupPrimitives)(unit, count); } primitiveProgress[unit].visible = visible; primitiveProgress[unit].references = clusterCount; #if PERF_HUD setupTime[threadIndex] += Timer::ticks() - startTick; #endif } break; case Task::PIXELS: { int unit = task[threadIndex].primitiveUnit; int visible = primitiveProgress[unit].visible; if(visible > 0) { int cluster = task[threadIndex].pixelCluster; Primitive *primitive = primitiveBatch[unit]; DrawCall *draw = drawList[pixelProgress[cluster].drawCall & DRAW_COUNT_BITS]; DrawData *data = draw->data; PixelProcessor::RoutinePointer pixelRoutine = draw->pixelPointer; pixelRoutine(primitive, visible, cluster, data); } finishRendering(task[threadIndex]); #if PERF_HUD pixelTime[threadIndex] += Timer::ticks() - startTick; #endif } break; case Task::RESUME: break; case Task::SUSPEND: break; default: ASSERT(false); } } void Renderer::synchronize() { sync->lock(sw::PUBLIC); sync->unlock(); } void Renderer::finishRendering(Task &pixelTask) { int unit = pixelTask.primitiveUnit; int cluster = pixelTask.pixelCluster; DrawCall &draw = *drawList[primitiveProgress[unit].drawCall & DRAW_COUNT_BITS]; DrawData &data = *draw.data; int primitive = primitiveProgress[unit].firstPrimitive; int count = primitiveProgress[unit].primitiveCount; int processedPrimitives = primitive + count; pixelProgress[cluster].processedPrimitives = processedPrimitives; if(pixelProgress[cluster].processedPrimitives >= draw.count) { ++pixelProgress[cluster].drawCall; // Atomic pixelProgress[cluster].processedPrimitives = 0; } int ref = primitiveProgress[unit].references--; // Atomic if(ref == 0) { ref = draw.references--; // Atomic if(ref == 0) { #if PERF_PROFILE for(int cluster = 0; cluster < clusterCount; cluster++) { for(int i = 0; i < PERF_TIMERS; i++) { profiler.cycles[i] += data.cycles[i][cluster]; } } #endif if(draw.queries) { for(auto &query : *(draw.queries)) { switch(query->type) { case Query::FRAGMENTS_PASSED: for(int cluster = 0; cluster < clusterCount; cluster++) { query->data += data.occlusion[cluster]; } break; case Query::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: query->data += processedPrimitives; break; default: break; } query->release(); } delete draw.queries; draw.queries = 0; } for(int i = 0; i < RENDERTARGETS; i++) { if(draw.renderTarget[i]) { draw.renderTarget[i]->unlockInternal(); } } if(draw.depthBuffer) { draw.depthBuffer->unlockInternal(); } if(draw.stencilBuffer) { draw.stencilBuffer->unlockStencil(); } for(int i = 0; i < TOTAL_IMAGE_UNITS; i++) { if(draw.texture[i]) { draw.texture[i]->unlock(); } } for(int i = 0; i < MAX_VERTEX_INPUTS; i++) { if(draw.vertexStream[i]) { draw.vertexStream[i]->unlock(); } } if(draw.indexBuffer) { draw.indexBuffer->unlock(); } for(int i = 0; i < MAX_UNIFORM_BUFFER_BINDINGS; i++) { if(draw.pUniformBuffers[i]) { draw.pUniformBuffers[i]->unlock(); } if(draw.vUniformBuffers[i]) { draw.vUniformBuffers[i]->unlock(); } } for(int i = 0; i < MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS; i++) { if(draw.transformFeedbackBuffers[i]) { draw.transformFeedbackBuffers[i]->unlock(); } } draw.vertexRoutine.reset(); draw.setupRoutine.reset(); draw.pixelRoutine.reset(); sync->unlock(); draw.references = -1; resumeApp->signal(); } } pixelProgress[cluster].executing = false; } void Renderer::processPrimitiveVertices(int unit, unsigned int start, unsigned int triangleCount, unsigned int loop, int thread) { Triangle *triangle = triangleBatch[unit]; int primitiveDrawCall = primitiveProgress[unit].drawCall; DrawCall *draw = drawList[primitiveDrawCall & DRAW_COUNT_BITS]; DrawData *data = draw->data; VertexTask *task = vertexTask[thread]; const void *indices = data->indices; VertexProcessor::RoutinePointer vertexRoutine = draw->vertexPointer; if(task->vertexCache.drawCall != primitiveDrawCall) { task->vertexCache.clear(); task->vertexCache.drawCall = primitiveDrawCall; } unsigned int batch[128][3]; // FIXME: Adjust to dynamic batch size switch(draw->drawType) { case DRAW_POINTLIST: { unsigned int index = start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index; batch[i][1] = index; batch[i][2] = index; index += 1; } } break; case DRAW_LINELIST: { unsigned int index = 2 * start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index + 0; batch[i][1] = index + 1; batch[i][2] = index + 1; index += 2; } } break; case DRAW_LINESTRIP: { unsigned int index = start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index + 0; batch[i][1] = index + 1; batch[i][2] = index + 1; index += 1; } } break; case DRAW_LINELOOP: { unsigned int index = start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = (index + 0) % loop; batch[i][1] = (index + 1) % loop; batch[i][2] = (index + 1) % loop; index += 1; } } break; case DRAW_TRIANGLELIST: { unsigned int index = 3 * start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index + 0; batch[i][1] = index + 1; batch[i][2] = index + 2; index += 3; } } break; case DRAW_TRIANGLESTRIP: { unsigned int index = start; for(unsigned int i = 0; i < triangleCount; i++) { if(leadingVertexFirst) { batch[i][0] = index + 0; batch[i][1] = index + (index & 1) + 1; batch[i][2] = index + (~index & 1) + 1; } else { batch[i][0] = index + (index & 1); batch[i][1] = index + (~index & 1); batch[i][2] = index + 2; } index += 1; } } break; case DRAW_TRIANGLEFAN: { unsigned int index = start; for(unsigned int i = 0; i < triangleCount; i++) { if(leadingVertexFirst) { batch[i][0] = index + 1; batch[i][1] = index + 2; batch[i][2] = 0; } else { batch[i][0] = 0; batch[i][1] = index + 1; batch[i][2] = index + 2; } index += 1; } } break; case DRAW_INDEXEDPOINTLIST8: { const unsigned char *index = (const unsigned char*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = *index; batch[i][1] = *index; batch[i][2] = *index; index += 1; } } break; case DRAW_INDEXEDPOINTLIST16: { const unsigned short *index = (const unsigned short*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = *index; batch[i][1] = *index; batch[i][2] = *index; index += 1; } } break; case DRAW_INDEXEDPOINTLIST32: { const unsigned int *index = (const unsigned int*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = *index; batch[i][1] = *index; batch[i][2] = *index; index += 1; } } break; case DRAW_INDEXEDLINELIST8: { const unsigned char *index = (const unsigned char*)indices + 2 * start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[1]; index += 2; } } break; case DRAW_INDEXEDLINELIST16: { const unsigned short *index = (const unsigned short*)indices + 2 * start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[1]; index += 2; } } break; case DRAW_INDEXEDLINELIST32: { const unsigned int *index = (const unsigned int*)indices + 2 * start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[1]; index += 2; } } break; case DRAW_INDEXEDLINESTRIP8: { const unsigned char *index = (const unsigned char*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[1]; index += 1; } } break; case DRAW_INDEXEDLINESTRIP16: { const unsigned short *index = (const unsigned short*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[1]; index += 1; } } break; case DRAW_INDEXEDLINESTRIP32: { const unsigned int *index = (const unsigned int*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[1]; index += 1; } } break; case DRAW_INDEXEDLINELOOP8: { const unsigned char *index = (const unsigned char*)indices; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[(start + i + 0) % loop]; batch[i][1] = index[(start + i + 1) % loop]; batch[i][2] = index[(start + i + 1) % loop]; } } break; case DRAW_INDEXEDLINELOOP16: { const unsigned short *index = (const unsigned short*)indices; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[(start + i + 0) % loop]; batch[i][1] = index[(start + i + 1) % loop]; batch[i][2] = index[(start + i + 1) % loop]; } } break; case DRAW_INDEXEDLINELOOP32: { const unsigned int *index = (const unsigned int*)indices; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[(start + i + 0) % loop]; batch[i][1] = index[(start + i + 1) % loop]; batch[i][2] = index[(start + i + 1) % loop]; } } break; case DRAW_INDEXEDTRIANGLELIST8: { const unsigned char *index = (const unsigned char*)indices + 3 * start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[2]; index += 3; } } break; case DRAW_INDEXEDTRIANGLELIST16: { const unsigned short *index = (const unsigned short*)indices + 3 * start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[2]; index += 3; } } break; case DRAW_INDEXEDTRIANGLELIST32: { const unsigned int *index = (const unsigned int*)indices + 3 * start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[1]; batch[i][2] = index[2]; index += 3; } } break; case DRAW_INDEXEDTRIANGLESTRIP8: { const unsigned char *index = (const unsigned char*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[((start + i) & 1) + 1]; batch[i][2] = index[(~(start + i) & 1) + 1]; index += 1; } } break; case DRAW_INDEXEDTRIANGLESTRIP16: { const unsigned short *index = (const unsigned short*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[((start + i) & 1) + 1]; batch[i][2] = index[(~(start + i) & 1) + 1]; index += 1; } } break; case DRAW_INDEXEDTRIANGLESTRIP32: { const unsigned int *index = (const unsigned int*)indices + start; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[0]; batch[i][1] = index[((start + i) & 1) + 1]; batch[i][2] = index[(~(start + i) & 1) + 1]; index += 1; } } break; case DRAW_INDEXEDTRIANGLEFAN8: { const unsigned char *index = (const unsigned char*)indices; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[start + i + 1]; batch[i][1] = index[start + i + 2]; batch[i][2] = index[0]; } } break; case DRAW_INDEXEDTRIANGLEFAN16: { const unsigned short *index = (const unsigned short*)indices; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[start + i + 1]; batch[i][1] = index[start + i + 2]; batch[i][2] = index[0]; } } break; case DRAW_INDEXEDTRIANGLEFAN32: { const unsigned int *index = (const unsigned int*)indices; for(unsigned int i = 0; i < triangleCount; i++) { batch[i][0] = index[start + i + 1]; batch[i][1] = index[start + i + 2]; batch[i][2] = index[0]; } } break; case DRAW_QUADLIST: { unsigned int index = 4 * start / 2; for(unsigned int i = 0; i < triangleCount; i += 2) { batch[i+0][0] = index + 0; batch[i+0][1] = index + 1; batch[i+0][2] = index + 2; batch[i+1][0] = index + 0; batch[i+1][1] = index + 2; batch[i+1][2] = index + 3; index += 4; } } break; default: ASSERT(false); return; } task->primitiveStart = start; task->vertexCount = triangleCount * 3; vertexRoutine(&triangle->v0, (unsigned int*)&batch, task, data); } int Renderer::setupSolidTriangles(int unit, int count) { Triangle *triangle = triangleBatch[unit]; Primitive *primitive = primitiveBatch[unit]; DrawCall &draw = *drawList[primitiveProgress[unit].drawCall & DRAW_COUNT_BITS]; SetupProcessor::State &state = draw.setupState; const SetupProcessor::RoutinePointer &setupRoutine = draw.setupPointer; int ms = state.multiSample; int pos = state.positionRegister; const DrawData *data = draw.data; int visible = 0; for(int i = 0; i < count; i++, triangle++) { Vertex &v0 = triangle->v0; Vertex &v1 = triangle->v1; Vertex &v2 = triangle->v2; if((v0.clipFlags & v1.clipFlags & v2.clipFlags) == Clipper::CLIP_FINITE) { Polygon polygon(&v0.v[pos], &v1.v[pos], &v2.v[pos]); int clipFlagsOr = v0.clipFlags | v1.clipFlags | v2.clipFlags | draw.clipFlags; if(clipFlagsOr != Clipper::CLIP_FINITE) { if(!clipper->clip(polygon, clipFlagsOr, draw)) { continue; } } if(setupRoutine(primitive, triangle, &polygon, data)) { primitive += ms; visible++; } } } return visible; } int Renderer::setupWireframeTriangle(int unit, int count) { Triangle *triangle = triangleBatch[unit]; Primitive *primitive = primitiveBatch[unit]; int visible = 0; DrawCall &draw = *drawList[primitiveProgress[unit].drawCall & DRAW_COUNT_BITS]; SetupProcessor::State &state = draw.setupState; const Vertex &v0 = triangle[0].v0; const Vertex &v1 = triangle[0].v1; const Vertex &v2 = triangle[0].v2; float d = (v0.y * v1.x - v0.x * v1.y) * v2.w + (v0.x * v2.y - v0.y * v2.x) * v1.w + (v2.x * v1.y - v1.x * v2.y) * v0.w; if(state.cullMode == CULL_CLOCKWISE) { if(d >= 0) return 0; } else if(state.cullMode == CULL_COUNTERCLOCKWISE) { if(d <= 0) return 0; } // Copy attributes triangle[1].v0 = v1; triangle[1].v1 = v2; triangle[2].v0 = v2; triangle[2].v1 = v0; if(state.color[0][0].flat) // FIXME { for(int i = 0; i < 2; i++) { triangle[1].v0.C[i] = triangle[0].v0.C[i]; triangle[1].v1.C[i] = triangle[0].v0.C[i]; triangle[2].v0.C[i] = triangle[0].v0.C[i]; triangle[2].v1.C[i] = triangle[0].v0.C[i]; } } for(int i = 0; i < 3; i++) { if(setupLine(*primitive, *triangle, draw)) { primitive->area = 0.5f * d; primitive++; visible++; } triangle++; } return visible; } int Renderer::setupVertexTriangle(int unit, int count) { Triangle *triangle = triangleBatch[unit]; Primitive *primitive = primitiveBatch[unit]; int visible = 0; DrawCall &draw = *drawList[primitiveProgress[unit].drawCall & DRAW_COUNT_BITS]; SetupProcessor::State &state = draw.setupState; const Vertex &v0 = triangle[0].v0; const Vertex &v1 = triangle[0].v1; const Vertex &v2 = triangle[0].v2; float d = (v0.y * v1.x - v0.x * v1.y) * v2.w + (v0.x * v2.y - v0.y * v2.x) * v1.w + (v2.x * v1.y - v1.x * v2.y) * v0.w; if(state.cullMode == CULL_CLOCKWISE) { if(d >= 0) return 0; } else if(state.cullMode == CULL_COUNTERCLOCKWISE) { if(d <= 0) return 0; } // Copy attributes triangle[1].v0 = v1; triangle[2].v0 = v2; for(int i = 0; i < 3; i++) { if(setupPoint(*primitive, *triangle, draw)) { primitive->area = 0.5f * d; primitive++; visible++; } triangle++; } return visible; } int Renderer::setupLines(int unit, int count) { Triangle *triangle = triangleBatch[unit]; Primitive *primitive = primitiveBatch[unit]; int visible = 0; DrawCall &draw = *drawList[primitiveProgress[unit].drawCall & DRAW_COUNT_BITS]; SetupProcessor::State &state = draw.setupState; int ms = state.multiSample; for(int i = 0; i < count; i++) { if(setupLine(*primitive, *triangle, draw)) { primitive += ms; visible++; } triangle++; } return visible; } int Renderer::setupPoints(int unit, int count) { Triangle *triangle = triangleBatch[unit]; Primitive *primitive = primitiveBatch[unit]; int visible = 0; DrawCall &draw = *drawList[primitiveProgress[unit].drawCall & DRAW_COUNT_BITS]; SetupProcessor::State &state = draw.setupState; int ms = state.multiSample; for(int i = 0; i < count; i++) { if(setupPoint(*primitive, *triangle, draw)) { primitive += ms; visible++; } triangle++; } return visible; } bool Renderer::setupLine(Primitive &primitive, Triangle &triangle, const DrawCall &draw) { const SetupProcessor::RoutinePointer &setupRoutine = draw.setupPointer; const SetupProcessor::State &state = draw.setupState; const DrawData &data = *draw.data; float lineWidth = data.lineWidth; Vertex &v0 = triangle.v0; Vertex &v1 = triangle.v1; int pos = state.positionRegister; const float4 &P0 = v0.v[pos]; const float4 &P1 = v1.v[pos]; if(P0.w <= 0 && P1.w <= 0) { return false; } const float W = data.Wx16[0] * (1.0f / 16.0f); const float H = data.Hx16[0] * (1.0f / 16.0f); float dx = W * (P1.x / P1.w - P0.x / P0.w); float dy = H * (P1.y / P1.w - P0.y / P0.w); if(dx == 0 && dy == 0) { return false; } if(state.multiSample > 1) { // Rectangle centered on the line segment float4 P[4]; int C[4]; P[0] = P0; P[1] = P1; P[2] = P1; P[3] = P0; float scale = lineWidth * 0.5f / sqrt(dx*dx + dy*dy); dx *= scale; dy *= scale; float dx0h = dx * P0.w / H; float dy0w = dy * P0.w / W; float dx1h = dx * P1.w / H; float dy1w = dy * P1.w / W; P[0].x += -dy0w; P[0].y += +dx0h; C[0] = clipper->computeClipFlags(P[0]); P[1].x += -dy1w; P[1].y += +dx1h; C[1] = clipper->computeClipFlags(P[1]); P[2].x += +dy1w; P[2].y += -dx1h; C[2] = clipper->computeClipFlags(P[2]); P[3].x += +dy0w; P[3].y += -dx0h; C[3] = clipper->computeClipFlags(P[3]); if((C[0] & C[1] & C[2] & C[3]) == Clipper::CLIP_FINITE) { Polygon polygon(P, 4); int clipFlagsOr = C[0] | C[1] | C[2] | C[3] | draw.clipFlags; if(clipFlagsOr != Clipper::CLIP_FINITE) { if(!clipper->clip(polygon, clipFlagsOr, draw)) { return false; } } return setupRoutine(&primitive, &triangle, &polygon, &data); } } else if(true) { // Connecting diamonds polygon // This shape satisfies the diamond test convention, except for the exit rule part. // Line segments with overlapping endpoints have duplicate fragments. // The ideal algorithm requires half-open line rasterization (b/80135519). float4 P[8]; int C[8]; P[0] = P0; P[1] = P0; P[2] = P0; P[3] = P0; P[4] = P1; P[5] = P1; P[6] = P1; P[7] = P1; float dx0 = lineWidth * 0.5f * P0.w / W; float dy0 = lineWidth * 0.5f * P0.w / H; float dx1 = lineWidth * 0.5f * P1.w / W; float dy1 = lineWidth * 0.5f * P1.w / H; P[0].x += -dx0; C[0] = clipper->computeClipFlags(P[0]); P[1].y += +dy0; C[1] = clipper->computeClipFlags(P[1]); P[2].x += +dx0; C[2] = clipper->computeClipFlags(P[2]); P[3].y += -dy0; C[3] = clipper->computeClipFlags(P[3]); P[4].x += -dx1; C[4] = clipper->computeClipFlags(P[4]); P[5].y += +dy1; C[5] = clipper->computeClipFlags(P[5]); P[6].x += +dx1; C[6] = clipper->computeClipFlags(P[6]); P[7].y += -dy1; C[7] = clipper->computeClipFlags(P[7]); if((C[0] & C[1] & C[2] & C[3] & C[4] & C[5] & C[6] & C[7]) == Clipper::CLIP_FINITE) { float4 L[6]; if(dx > -dy) { if(dx > dy) // Right { L[0] = P[0]; L[1] = P[1]; L[2] = P[5]; L[3] = P[6]; L[4] = P[7]; L[5] = P[3]; } else // Down { L[0] = P[0]; L[1] = P[4]; L[2] = P[5]; L[3] = P[6]; L[4] = P[2]; L[5] = P[3]; } } else { if(dx > dy) // Up { L[0] = P[0]; L[1] = P[1]; L[2] = P[2]; L[3] = P[6]; L[4] = P[7]; L[5] = P[4]; } else // Left { L[0] = P[1]; L[1] = P[2]; L[2] = P[3]; L[3] = P[7]; L[4] = P[4]; L[5] = P[5]; } } Polygon polygon(L, 6); int clipFlagsOr = C[0] | C[1] | C[2] | C[3] | C[4] | C[5] | C[6] | C[7] | draw.clipFlags; if(clipFlagsOr != Clipper::CLIP_FINITE) { if(!clipper->clip(polygon, clipFlagsOr, draw)) { return false; } } return setupRoutine(&primitive, &triangle, &polygon, &data); } } else { // Parallelogram approximating Bresenham line // This algorithm does not satisfy the ideal diamond-exit rule, but does avoid the // duplicate fragment rasterization problem and satisfies all of Vulkan's minimum // requirements for Bresenham line segment rasterization. float4 P[8]; P[0] = P0; P[1] = P0; P[2] = P0; P[3] = P0; P[4] = P1; P[5] = P1; P[6] = P1; P[7] = P1; float dx0 = lineWidth * 0.5f * P0.w / W; float dy0 = lineWidth * 0.5f * P0.w / H; float dx1 = lineWidth * 0.5f * P1.w / W; float dy1 = lineWidth * 0.5f * P1.w / H; P[0].x += -dx0; P[1].y += +dy0; P[2].x += +dx0; P[3].y += -dy0; P[4].x += -dx1; P[5].y += +dy1; P[6].x += +dx1; P[7].y += -dy1; float4 L[4]; if(dx > -dy) { if(dx > dy) // Right { L[0] = P[1]; L[1] = P[5]; L[2] = P[7]; L[3] = P[3]; } else // Down { L[0] = P[0]; L[1] = P[4]; L[2] = P[6]; L[3] = P[2]; } } else { if(dx > dy) // Up { L[0] = P[0]; L[1] = P[2]; L[2] = P[6]; L[3] = P[4]; } else // Left { L[0] = P[1]; L[1] = P[3]; L[2] = P[7]; L[3] = P[5]; } } int C0 = clipper->computeClipFlags(L[0]); int C1 = clipper->computeClipFlags(L[1]); int C2 = clipper->computeClipFlags(L[2]); int C3 = clipper->computeClipFlags(L[3]); if((C0 & C1 & C2 & C3) == Clipper::CLIP_FINITE) { Polygon polygon(L, 4); int clipFlagsOr = C0 | C1 | C2 | C3; if(clipFlagsOr != Clipper::CLIP_FINITE) { if(!clipper->clip(polygon, clipFlagsOr, draw)) { return false; } } return setupRoutine(&primitive, &triangle, &polygon, &data); } } return false; } bool Renderer::setupPoint(Primitive &primitive, Triangle &triangle, const DrawCall &draw) { const SetupProcessor::RoutinePointer &setupRoutine = draw.setupPointer; const SetupProcessor::State &state = draw.setupState; const DrawData &data = *draw.data; Vertex &v = triangle.v0; float pSize; int pts = state.pointSizeRegister; if(state.pointSizeRegister != Unused) { pSize = v.v[pts].y; } else { pSize = data.point.pointSize[0]; } pSize = clamp(pSize, data.point.pointSizeMin, data.point.pointSizeMax); float4 P[4]; int C[4]; int pos = state.positionRegister; P[0] = v.v[pos]; P[1] = v.v[pos]; P[2] = v.v[pos]; P[3] = v.v[pos]; const float X = pSize * P[0].w * data.halfPixelX[0]; const float Y = pSize * P[0].w * data.halfPixelY[0]; P[0].x -= X; P[0].y += Y; C[0] = clipper->computeClipFlags(P[0]); P[1].x += X; P[1].y += Y; C[1] = clipper->computeClipFlags(P[1]); P[2].x += X; P[2].y -= Y; C[2] = clipper->computeClipFlags(P[2]); P[3].x -= X; P[3].y -= Y; C[3] = clipper->computeClipFlags(P[3]); Polygon polygon(P, 4); if((C[0] & C[1] & C[2] & C[3]) == Clipper::CLIP_FINITE) { int clipFlagsOr = C[0] | C[1] | C[2] | C[3] | draw.clipFlags; if(clipFlagsOr != Clipper::CLIP_FINITE) { if(!clipper->clip(polygon, clipFlagsOr, draw)) { return false; } } triangle.v1 = triangle.v0; triangle.v2 = triangle.v0; triangle.v1.X += iround(16 * 0.5f * pSize); triangle.v2.Y -= iround(16 * 0.5f * pSize) * (data.Hx16[0] > 0.0f ? 1 : -1); // Both Direct3D and OpenGL expect (0, 0) in the top-left corner return setupRoutine(&primitive, &triangle, &polygon, &data); } return false; } void Renderer::initializeThreads() { unitCount = ceilPow2(threadCount); clusterCount = ceilPow2(threadCount); for(int i = 0; i < unitCount; i++) { triangleBatch[i] = (Triangle*)allocate(batchSize * sizeof(Triangle)); primitiveBatch[i] = (Primitive*)allocate(batchSize * sizeof(Primitive)); } for(int i = 0; i < threadCount; i++) { vertexTask[i] = (VertexTask*)allocate(sizeof(VertexTask)); vertexTask[i]->vertexCache.drawCall = -1; task[i].type = Task::SUSPEND; resume[i] = new Event(); suspend[i] = new Event(); Parameters parameters; parameters.threadIndex = i; parameters.renderer = this; exitThreads = false; worker[i] = new Thread(threadFunction, ¶meters); suspend[i]->wait(); suspend[i]->signal(); } } void Renderer::terminateThreads() { while(threadsAwake != 0) { Thread::sleep(1); } for(int thread = 0; thread < threadCount; thread++) { if(worker[thread]) { exitThreads = true; resume[thread]->signal(); worker[thread]->join(); delete worker[thread]; worker[thread] = 0; delete resume[thread]; resume[thread] = 0; delete suspend[thread]; suspend[thread] = 0; } deallocate(vertexTask[thread]); vertexTask[thread] = 0; } for(int i = 0; i < 16; i++) { deallocate(triangleBatch[i]); triangleBatch[i] = 0; deallocate(primitiveBatch[i]); primitiveBatch[i] = 0; } } void Renderer::loadConstants(const VertexShader *vertexShader) { if(!vertexShader) return; size_t count = vertexShader->getLength(); for(size_t i = 0; i < count; i++) { const Shader::Instruction *instruction = vertexShader->getInstruction(i); if(instruction->opcode == Shader::OPCODE_DEF) { int index = instruction->dst.index; float value[4]; value[0] = instruction->src[0].value[0]; value[1] = instruction->src[0].value[1]; value[2] = instruction->src[0].value[2]; value[3] = instruction->src[0].value[3]; setVertexShaderConstantF(index, value); } else if(instruction->opcode == Shader::OPCODE_DEFI) { int index = instruction->dst.index; int integer[4]; integer[0] = instruction->src[0].integer[0]; integer[1] = instruction->src[0].integer[1]; integer[2] = instruction->src[0].integer[2]; integer[3] = instruction->src[0].integer[3]; setVertexShaderConstantI(index, integer); } else if(instruction->opcode == Shader::OPCODE_DEFB) { int index = instruction->dst.index; int boolean = instruction->src[0].boolean[0]; setVertexShaderConstantB(index, &boolean); } } } void Renderer::loadConstants(const PixelShader *pixelShader) { if(!pixelShader) return; size_t count = pixelShader->getLength(); for(size_t i = 0; i < count; i++) { const Shader::Instruction *instruction = pixelShader->getInstruction(i); if(instruction->opcode == Shader::OPCODE_DEF) { int index = instruction->dst.index; float value[4]; value[0] = instruction->src[0].value[0]; value[1] = instruction->src[0].value[1]; value[2] = instruction->src[0].value[2]; value[3] = instruction->src[0].value[3]; setPixelShaderConstantF(index, value); } else if(instruction->opcode == Shader::OPCODE_DEFI) { int index = instruction->dst.index; int integer[4]; integer[0] = instruction->src[0].integer[0]; integer[1] = instruction->src[0].integer[1]; integer[2] = instruction->src[0].integer[2]; integer[3] = instruction->src[0].integer[3]; setPixelShaderConstantI(index, integer); } else if(instruction->opcode == Shader::OPCODE_DEFB) { int index = instruction->dst.index; int boolean = instruction->src[0].boolean[0]; setPixelShaderConstantB(index, &boolean); } } } void Renderer::setIndexBuffer(Resource *indexBuffer) { context->indexBuffer = indexBuffer; } void Renderer::setMultiSampleMask(unsigned int mask) { context->sampleMask = mask; } void Renderer::setTransparencyAntialiasing(TransparencyAntialiasing transparencyAntialiasing) { sw::transparencyAntialiasing = transparencyAntialiasing; } bool Renderer::isReadWriteTexture(int sampler) { for(int index = 0; index < RENDERTARGETS; index++) { if(context->renderTarget[index] && context->texture[sampler] == context->renderTarget[index]->getResource()) { return true; } } if(context->depthBuffer && context->texture[sampler] == context->depthBuffer->getResource()) { return true; } return false; } void Renderer::updateClipper() { if(updateClipPlanes) { if(VertexProcessor::isFixedFunction()) // User plane in world space { const Matrix &scissorWorld = getViewTransform(); if(clipFlags & Clipper::CLIP_PLANE0) clipPlane[0] = scissorWorld * userPlane[0]; if(clipFlags & Clipper::CLIP_PLANE1) clipPlane[1] = scissorWorld * userPlane[1]; if(clipFlags & Clipper::CLIP_PLANE2) clipPlane[2] = scissorWorld * userPlane[2]; if(clipFlags & Clipper::CLIP_PLANE3) clipPlane[3] = scissorWorld * userPlane[3]; if(clipFlags & Clipper::CLIP_PLANE4) clipPlane[4] = scissorWorld * userPlane[4]; if(clipFlags & Clipper::CLIP_PLANE5) clipPlane[5] = scissorWorld * userPlane[5]; } else // User plane in clip space { if(clipFlags & Clipper::CLIP_PLANE0) clipPlane[0] = userPlane[0]; if(clipFlags & Clipper::CLIP_PLANE1) clipPlane[1] = userPlane[1]; if(clipFlags & Clipper::CLIP_PLANE2) clipPlane[2] = userPlane[2]; if(clipFlags & Clipper::CLIP_PLANE3) clipPlane[3] = userPlane[3]; if(clipFlags & Clipper::CLIP_PLANE4) clipPlane[4] = userPlane[4]; if(clipFlags & Clipper::CLIP_PLANE5) clipPlane[5] = userPlane[5]; } updateClipPlanes = false; } } void Renderer::setTextureResource(unsigned int sampler, Resource *resource) { ASSERT(sampler < TOTAL_IMAGE_UNITS); context->texture[sampler] = resource; } void Renderer::setTextureLevel(unsigned int sampler, unsigned int face, unsigned int level, Surface *surface, TextureType type) { ASSERT(sampler < TOTAL_IMAGE_UNITS && face < 6 && level < MIPMAP_LEVELS); context->sampler[sampler].setTextureLevel(face, level, surface, type); } void Renderer::setTextureFilter(SamplerType type, int sampler, FilterType textureFilter) { if(type == SAMPLER_PIXEL) { PixelProcessor::setTextureFilter(sampler, textureFilter); } else { VertexProcessor::setTextureFilter(sampler, textureFilter); } } void Renderer::setMipmapFilter(SamplerType type, int sampler, MipmapType mipmapFilter) { if(type == SAMPLER_PIXEL) { PixelProcessor::setMipmapFilter(sampler, mipmapFilter); } else { VertexProcessor::setMipmapFilter(sampler, mipmapFilter); } } void Renderer::setGatherEnable(SamplerType type, int sampler, bool enable) { if(type == SAMPLER_PIXEL) { PixelProcessor::setGatherEnable(sampler, enable); } else { VertexProcessor::setGatherEnable(sampler, enable); } } void Renderer::setAddressingModeU(SamplerType type, int sampler, AddressingMode addressMode) { if(type == SAMPLER_PIXEL) { PixelProcessor::setAddressingModeU(sampler, addressMode); } else { VertexProcessor::setAddressingModeU(sampler, addressMode); } } void Renderer::setAddressingModeV(SamplerType type, int sampler, AddressingMode addressMode) { if(type == SAMPLER_PIXEL) { PixelProcessor::setAddressingModeV(sampler, addressMode); } else { VertexProcessor::setAddressingModeV(sampler, addressMode); } } void Renderer::setAddressingModeW(SamplerType type, int sampler, AddressingMode addressMode) { if(type == SAMPLER_PIXEL) { PixelProcessor::setAddressingModeW(sampler, addressMode); } else { VertexProcessor::setAddressingModeW(sampler, addressMode); } } void Renderer::setReadSRGB(SamplerType type, int sampler, bool sRGB) { if(type == SAMPLER_PIXEL) { PixelProcessor::setReadSRGB(sampler, sRGB); } else { VertexProcessor::setReadSRGB(sampler, sRGB); } } void Renderer::setMipmapLOD(SamplerType type, int sampler, float bias) { if(type == SAMPLER_PIXEL) { PixelProcessor::setMipmapLOD(sampler, bias); } else { VertexProcessor::setMipmapLOD(sampler, bias); } } void Renderer::setBorderColor(SamplerType type, int sampler, const Color &borderColor) { if(type == SAMPLER_PIXEL) { PixelProcessor::setBorderColor(sampler, borderColor); } else { VertexProcessor::setBorderColor(sampler, borderColor); } } void Renderer::setMaxAnisotropy(SamplerType type, int sampler, float maxAnisotropy) { if(type == SAMPLER_PIXEL) { PixelProcessor::setMaxAnisotropy(sampler, maxAnisotropy); } else { VertexProcessor::setMaxAnisotropy(sampler, maxAnisotropy); } } void Renderer::setHighPrecisionFiltering(SamplerType type, int sampler, bool highPrecisionFiltering) { if(type == SAMPLER_PIXEL) { PixelProcessor::setHighPrecisionFiltering(sampler, highPrecisionFiltering); } else { VertexProcessor::setHighPrecisionFiltering(sampler, highPrecisionFiltering); } } void Renderer::setSwizzleR(SamplerType type, int sampler, SwizzleType swizzleR) { if(type == SAMPLER_PIXEL) { PixelProcessor::setSwizzleR(sampler, swizzleR); } else { VertexProcessor::setSwizzleR(sampler, swizzleR); } } void Renderer::setSwizzleG(SamplerType type, int sampler, SwizzleType swizzleG) { if(type == SAMPLER_PIXEL) { PixelProcessor::setSwizzleG(sampler, swizzleG); } else { VertexProcessor::setSwizzleG(sampler, swizzleG); } } void Renderer::setSwizzleB(SamplerType type, int sampler, SwizzleType swizzleB) { if(type == SAMPLER_PIXEL) { PixelProcessor::setSwizzleB(sampler, swizzleB); } else { VertexProcessor::setSwizzleB(sampler, swizzleB); } } void Renderer::setSwizzleA(SamplerType type, int sampler, SwizzleType swizzleA) { if(type == SAMPLER_PIXEL) { PixelProcessor::setSwizzleA(sampler, swizzleA); } else { VertexProcessor::setSwizzleA(sampler, swizzleA); } } void Renderer::setCompareFunc(SamplerType type, int sampler, CompareFunc compFunc) { if(type == SAMPLER_PIXEL) { PixelProcessor::setCompareFunc(sampler, compFunc); } else { VertexProcessor::setCompareFunc(sampler, compFunc); } } void Renderer::setBaseLevel(SamplerType type, int sampler, int baseLevel) { if(type == SAMPLER_PIXEL) { PixelProcessor::setBaseLevel(sampler, baseLevel); } else { VertexProcessor::setBaseLevel(sampler, baseLevel); } } void Renderer::setMaxLevel(SamplerType type, int sampler, int maxLevel) { if(type == SAMPLER_PIXEL) { PixelProcessor::setMaxLevel(sampler, maxLevel); } else { VertexProcessor::setMaxLevel(sampler, maxLevel); } } void Renderer::setMinLod(SamplerType type, int sampler, float minLod) { if(type == SAMPLER_PIXEL) { PixelProcessor::setMinLod(sampler, minLod); } else { VertexProcessor::setMinLod(sampler, minLod); } } void Renderer::setMaxLod(SamplerType type, int sampler, float maxLod) { if(type == SAMPLER_PIXEL) { PixelProcessor::setMaxLod(sampler, maxLod); } else { VertexProcessor::setMaxLod(sampler, maxLod); } } void Renderer::setSyncRequired(SamplerType type, int sampler, bool syncRequired) { if(type == SAMPLER_PIXEL) { PixelProcessor::setSyncRequired(sampler, syncRequired); } else { VertexProcessor::setSyncRequired(sampler, syncRequired); } } void Renderer::setPointSpriteEnable(bool pointSpriteEnable) { context->setPointSpriteEnable(pointSpriteEnable); } void Renderer::setPointScaleEnable(bool pointScaleEnable) { context->setPointScaleEnable(pointScaleEnable); } void Renderer::setLineWidth(float width) { context->lineWidth = width; } void Renderer::setDepthBias(float bias) { context->depthBias = bias; } void Renderer::setSlopeDepthBias(float slopeBias) { context->slopeDepthBias = slopeBias; } void Renderer::setRasterizerDiscard(bool rasterizerDiscard) { context->rasterizerDiscard = rasterizerDiscard; } void Renderer::setPixelShader(const PixelShader *shader) { context->pixelShader = shader; loadConstants(shader); } void Renderer::setVertexShader(const VertexShader *shader) { context->vertexShader = shader; loadConstants(shader); } void Renderer::setPixelShaderConstantF(unsigned int index, const float value[4], unsigned int count) { for(unsigned int i = 0; i < DRAW_COUNT; i++) { if(drawCall[i]->psDirtyConstF < index + count) { drawCall[i]->psDirtyConstF = index + count; } } for(unsigned int i = 0; i < count; i++) { PixelProcessor::setFloatConstant(index + i, value); value += 4; } } void Renderer::setPixelShaderConstantI(unsigned int index, const int value[4], unsigned int count) { for(unsigned int i = 0; i < DRAW_COUNT; i++) { if(drawCall[i]->psDirtyConstI < index + count) { drawCall[i]->psDirtyConstI = index + count; } } for(unsigned int i = 0; i < count; i++) { PixelProcessor::setIntegerConstant(index + i, value); value += 4; } } void Renderer::setPixelShaderConstantB(unsigned int index, const int *boolean, unsigned int count) { for(unsigned int i = 0; i < DRAW_COUNT; i++) { if(drawCall[i]->psDirtyConstB < index + count) { drawCall[i]->psDirtyConstB = index + count; } } for(unsigned int i = 0; i < count; i++) { PixelProcessor::setBooleanConstant(index + i, *boolean); boolean++; } } void Renderer::setVertexShaderConstantF(unsigned int index, const float value[4], unsigned int count) { for(unsigned int i = 0; i < DRAW_COUNT; i++) { if(drawCall[i]->vsDirtyConstF < index + count) { drawCall[i]->vsDirtyConstF = index + count; } } for(unsigned int i = 0; i < count; i++) { VertexProcessor::setFloatConstant(index + i, value); value += 4; } } void Renderer::setVertexShaderConstantI(unsigned int index, const int value[4], unsigned int count) { for(unsigned int i = 0; i < DRAW_COUNT; i++) { if(drawCall[i]->vsDirtyConstI < index + count) { drawCall[i]->vsDirtyConstI = index + count; } } for(unsigned int i = 0; i < count; i++) { VertexProcessor::setIntegerConstant(index + i, value); value += 4; } } void Renderer::setVertexShaderConstantB(unsigned int index, const int *boolean, unsigned int count) { for(unsigned int i = 0; i < DRAW_COUNT; i++) { if(drawCall[i]->vsDirtyConstB < index + count) { drawCall[i]->vsDirtyConstB = index + count; } } for(unsigned int i = 0; i < count; i++) { VertexProcessor::setBooleanConstant(index + i, *boolean); boolean++; } } void Renderer::setModelMatrix(const Matrix &M, int i) { VertexProcessor::setModelMatrix(M, i); } void Renderer::setViewMatrix(const Matrix &V) { VertexProcessor::setViewMatrix(V); updateClipPlanes = true; } void Renderer::setBaseMatrix(const Matrix &B) { VertexProcessor::setBaseMatrix(B); updateClipPlanes = true; } void Renderer::setProjectionMatrix(const Matrix &P) { VertexProcessor::setProjectionMatrix(P); updateClipPlanes = true; } void Renderer::addQuery(Query *query) { queries.push_back(query); } void Renderer::removeQuery(Query *query) { queries.remove(query); } #if PERF_HUD int Renderer::getThreadCount() { return threadCount; } int64_t Renderer::getVertexTime(int thread) { return vertexTime[thread]; } int64_t Renderer::getSetupTime(int thread) { return setupTime[thread]; } int64_t Renderer::getPixelTime(int thread) { return pixelTime[thread]; } void Renderer::resetTimers() { for(int thread = 0; thread < threadCount; thread++) { vertexTime[thread] = 0; setupTime[thread] = 0; pixelTime[thread] = 0; } } #endif void Renderer::setViewport(const Viewport &viewport) { this->viewport = viewport; } void Renderer::setScissor(const Rect &scissor) { this->scissor = scissor; } void Renderer::setClipFlags(int flags) { clipFlags = flags << 8; // Bottom 8 bits used by legacy frustum } void Renderer::setClipPlane(unsigned int index, const float plane[4]) { if(index < MAX_CLIP_PLANES) { userPlane[index] = plane; } else ASSERT(false); updateClipPlanes = true; } void Renderer::updateConfiguration(bool initialUpdate) { bool newConfiguration = swiftConfig->hasNewConfiguration(); if(newConfiguration || initialUpdate) { terminateThreads(); SwiftConfig::Configuration configuration = {}; swiftConfig->getConfiguration(configuration); precacheVertex = !newConfiguration && configuration.precache; precacheSetup = !newConfiguration && configuration.precache; precachePixel = !newConfiguration && configuration.precache; VertexProcessor::setRoutineCacheSize(configuration.vertexRoutineCacheSize); PixelProcessor::setRoutineCacheSize(configuration.pixelRoutineCacheSize); SetupProcessor::setRoutineCacheSize(configuration.setupRoutineCacheSize); switch(configuration.textureSampleQuality) { case 0: Sampler::setFilterQuality(FILTER_POINT); break; case 1: Sampler::setFilterQuality(FILTER_LINEAR); break; case 2: Sampler::setFilterQuality(FILTER_ANISOTROPIC); break; default: Sampler::setFilterQuality(FILTER_ANISOTROPIC); break; } switch(configuration.mipmapQuality) { case 0: Sampler::setMipmapQuality(MIPMAP_POINT); break; case 1: Sampler::setMipmapQuality(MIPMAP_LINEAR); break; default: Sampler::setMipmapQuality(MIPMAP_LINEAR); break; } setPerspectiveCorrection(configuration.perspectiveCorrection); switch(configuration.transcendentalPrecision) { case 0: logPrecision = APPROXIMATE; expPrecision = APPROXIMATE; rcpPrecision = APPROXIMATE; rsqPrecision = APPROXIMATE; break; case 1: logPrecision = PARTIAL; expPrecision = PARTIAL; rcpPrecision = PARTIAL; rsqPrecision = PARTIAL; break; case 2: logPrecision = ACCURATE; expPrecision = ACCURATE; rcpPrecision = ACCURATE; rsqPrecision = ACCURATE; break; case 3: logPrecision = WHQL; expPrecision = WHQL; rcpPrecision = WHQL; rsqPrecision = WHQL; break; case 4: logPrecision = IEEE; expPrecision = IEEE; rcpPrecision = IEEE; rsqPrecision = IEEE; break; default: logPrecision = ACCURATE; expPrecision = ACCURATE; rcpPrecision = ACCURATE; rsqPrecision = ACCURATE; break; } switch(configuration.transparencyAntialiasing) { case 0: transparencyAntialiasing = TRANSPARENCY_NONE; break; case 1: transparencyAntialiasing = TRANSPARENCY_ALPHA_TO_COVERAGE; break; default: transparencyAntialiasing = TRANSPARENCY_NONE; break; } switch(configuration.threadCount) { case -1: threadCount = CPUID::coreCount(); break; case 0: threadCount = CPUID::processAffinity(); break; default: threadCount = configuration.threadCount; break; } CPUID::setEnableSSE4_1(configuration.enableSSE4_1); CPUID::setEnableSSSE3(configuration.enableSSSE3); CPUID::setEnableSSE3(configuration.enableSSE3); CPUID::setEnableSSE2(configuration.enableSSE2); CPUID::setEnableSSE(configuration.enableSSE); rr::Config::Edit cfg; cfg.clearOptimizationPasses(); for(auto pass : configuration.optimization) { if (pass != rr::Optimization::Pass::Disabled) { cfg.add(pass); } } rr::Nucleus::adjustDefaultConfig(cfg); forceWindowed = configuration.forceWindowed; complementaryDepthBuffer = configuration.complementaryDepthBuffer; postBlendSRGB = configuration.postBlendSRGB; exactColorRounding = configuration.exactColorRounding; forceClearRegisters = configuration.forceClearRegisters; #ifndef NDEBUG minPrimitives = configuration.minPrimitives; maxPrimitives = configuration.maxPrimitives; #endif } if(!initialUpdate && !worker[0]) { initializeThreads(); } } }