2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include "drawable-view-native-renderer.h"
19 #include <dali/public-api/signals/render-callback.h>
26 // GLES3+ is required for this to work!
27 #include <GLES3/gl3.h>
29 #include <EGL/eglext.h>
40 auto err = glGetError(); \
43 printf("%p:%d: ERROR: 0x%X\n", this, __LINE__, int(err)); \
50 * Vertices of quad to display when using offscreen rendering
52 constexpr float QUAD_VERTS[] = {
53 // positions // colors // texture coords
61 * Indices of quad for offscreen rendering
63 constexpr unsigned short QUAD_INDICES[] = {
69 * UV coords of quad for offscreen rendering
71 constexpr float QUAD_UV[] = {
72 // positions // colors // texture coords
73 1.0f, 1.0f, // top right
74 1.0f, 0.0f, // bottom right
75 0.0f, 0.0f, // bottom left
76 0.0f, 1.0f // top left
80 namespace Dali::Internal
82 struct DrawableViewNativeRenderer::Impl
85 * This structure associates framebuffer with texture and fence object
87 struct FrameBufferTexture
89 uint32_t textureId{0u};
90 uint32_t framebufferId{0u};
91 GLsync fence{nullptr};
95 bool DequeueTextureDrawBuffer( uint32_t& outIndex )
97 std::scoped_lock<std::recursive_mutex> lock(mTextureQueueMutex);
98 if(mTextureDrawQueue.empty())
100 // TODO: probably add textures if necessary
104 auto retval = mTextureDrawQueue.front();
105 mTextureDrawQueue.pop_front();
111 * Enqueues framebuffer for the Read queue to be used by
114 void EnqueueTextureReadBuffer(uint32_t fbId)
116 // push ready texture to front of 'read' queue
117 std::scoped_lock<std::recursive_mutex> lock(mTextureQueueMutex);
119 auto& fb = mFramebufferTexture[fbId];
121 // Check state of fence whether the texture can be passed to the CONSUMER
124 auto checkFenceState = glClientWaitSync(fb.fence, GL_SYNC_FLUSH_COMMANDS_BIT, 0);
125 if(checkFenceState == GL_ALREADY_SIGNALED || checkFenceState == GL_CONDITION_SATISFIED)
127 // Ready so push directly to Read queue
128 mTextureReadQueue.push_back(fbId);
132 // Still busy so push to Stage queue
133 mTextureStageQueue.push_back(fbId);
138 void EnqueueTextureDrawBuffer(uint32_t fbId)
140 // push ready texture to front of 'read' queue
141 std::scoped_lock<std::recursive_mutex> lock(mTextureQueueMutex);
142 mTextureDrawQueue.push_back(fbId);
145 int32_t DequeueTextureReadBuffer(FrameBufferTexture& framebufferTexture)
147 // executed by DALi RenderThread!
148 std::deque<uint32_t> backTextures;
149 std::scoped_lock<std::recursive_mutex> lock(mTextureQueueMutex);
151 if(mTextureReadQueue.empty())
153 EnqueueStagedTexture();
157 while(!mTextureStageQueue.empty())
159 // we have something to render, so discard
160 auto stagedId = mTextureStageQueue.back();
161 EnqueueTextureDrawBuffer(stagedId);
162 mTextureStageQueue.pop_back();
166 if(mTextureReadQueue.empty())
171 auto retval = mTextureReadQueue.back();
172 mTextureReadQueue.pop_back();
174 // drain all back images and return them to the 'draw' queue
175 // and remove old images
176 while(!mTextureReadQueue.empty())
178 auto texId = mTextureReadQueue.back();
179 if(framebufferTexture.fence)
181 glDeleteSync(framebufferTexture.fence);
182 framebufferTexture.fence = nullptr;
184 mTextureDrawQueue.push_front(texId);
185 mTextureReadQueue.pop_back();
188 return int32_t(retval);
192 * Enqueues previously staged texture
194 uint32_t EnqueueStagedTexture()
197 std::deque<uint32_t> stagedQueue;
200 while(!mTextureStageQueue.empty())
202 auto stagedId = mTextureStageQueue.front();
203 auto& fb = mFramebufferTexture[stagedId];
206 auto syncResult = glClientWaitSync(fb.fence, GL_SYNC_FLUSH_COMMANDS_BIT, 0);
207 if(syncResult == GL_CONDITION_SATISFIED || syncResult == GL_ALREADY_SIGNALED)
209 // push texture into the queue
210 mTextureReadQueue.push_back(stagedId);
216 stagedQueue.push_back(stagedId);
221 stagedQueue.push_back(stagedId);
223 mTextureStageQueue.pop_front();
225 mTextureStageQueue = std::move(stagedQueue);
229 uint32_t CreateFramebuffer(uint32_t index, uint32_t width, uint32_t height)
231 auto& fb = mFramebufferTexture[index];
232 if(!fb.framebufferId)
234 GLuint offscreenFramebuffer, renderBuffer;
235 GL(glGenFramebuffers(1, &offscreenFramebuffer))
236 GL(glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebuffer));
237 GL(glGenRenderbuffers(1, &renderBuffer));
238 GL(glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer));
239 GL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.textureId, 0));
240 GL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height))
241 GL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderBuffer));
242 fb.framebufferId = offscreenFramebuffer;
244 [[maybe_unused]] auto result = glCheckFramebufferStatus(GL_FRAMEBUFFER);
245 DALI_ASSERT_ALWAYS( result == GL_FRAMEBUFFER_COMPLETE && "Framebuffer incomplete!");
246 return offscreenFramebuffer;
255 explicit Impl(const NativeRendererCreateInfo& renderCreateInfo)
256 : mCreateInfo(renderCreateInfo)
273 * Initializes renderer thread for offscreen rendering
275 void InitializeThread()
277 mThread = std::make_unique<std::thread>(&Impl::StartThread, this);
280 void PushRenderCallbackInputData( const Dali::RenderCallbackInput& renderCallbackInput )
283 std::scoped_lock<std::mutex> lock(mRenderCallbackInputDataMutex);
284 mRenderCallbackInputData = renderCallbackInput;
287 void PopRenderCallbackInputData( Dali::RenderCallbackInput& renderCallbackInput )
289 std::scoped_lock<std::mutex> lock(mRenderCallbackInputDataMutex);
290 renderCallbackInput = mRenderCallbackInputData;
299 * Function initializes thread for parallel rendering.
301 * The internal loop runs until the private EGL context has been
308 // We need to acquire shared context, while this is not done
309 // it's necessary to wait for context to be bound.
310 while(mRunning && !mEglContextBound)
312 // Wait for context to be given
317 if(!eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext))
319 [[maybe_unused]] auto err = eglGetError();
320 printf("%d\n", int(err));
322 mEglContextBound = true;
325 InitializeOffscreenFramebuffers();
333 * Reinitializes offscreen framebuffers and textures in case
334 * the resize has been requested.
336 void ReinitializeFramebuffers()
339 for(auto& fb : mFramebufferTexture)
343 GL(glDeleteSync(fb.fence));
347 GL(glDeleteFramebuffers(1, &fb.framebufferId))
348 fb.framebufferId = 0u;
352 GL(glDeleteTextures(1, &fb.textureId))
355 fb.textureId = CreateOffscreenTexture( mWidth, mHeight );
356 fb.framebufferId = CreateFramebuffer( index, mWidth, mHeight );
361 void ThreadRunRender()
365 // If there is a resize request waiting, then recreate all framebuffers
368 ReinitializeFramebuffers();
369 mResizeRequest = false;
372 Dali::RenderCallbackInput input;
374 PopRenderCallbackInputData( input );
377 auto result = DequeueTextureDrawBuffer(index);
383 auto& fb = mFramebufferTexture[index];
384 GL(glBindFramebuffer(GL_FRAMEBUFFER, fb.framebufferId))
389 if(mOnRenderCallback)
391 CallbackBase::ExecuteReturn<int>(*mOnRenderCallback);
394 // If the framebuffer is guarded with fence object then
395 // delete it as at this point it is no longer valid.
398 // Make sure GPU finished
399 glDeleteSync(fb.fence);
403 // Inject sync object into the GL commands stream
404 fb.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
405 EnqueueTextureReadBuffer(index);
406 glBindFramebuffer(GL_FRAMEBUFFER, 0);
410 void Resize(uint32_t width, uint32_t height )
414 mResizeRequest = true;
417 uint32_t mWidth {0u};
418 uint32_t mHeight {0u};
419 std::atomic_bool mResizeRequest { false };
422 * Clones current EGL context, this function must be called from the render callback
423 * and be executed on the DALi RenderThread
425 void CloneEglContext()
427 // extract shared context (void*)
428 auto context = eglGetCurrentContext();
433 eglGetConfigs(mEglDisplay, nullptr, 0, &size);
434 std::vector<EGLConfig> configs;
435 configs.resize(size);
436 eglGetConfigs(mEglDisplay, configs.data(), EGLint(configs.size()), &size);
438 // Find out which config is used by current context
439 eglQueryContext(mEglDisplay, context, EGL_CONFIG_ID, &configId);
442 auto version = int(30); // TODO: get context version and select the same one
443 std::vector<EGLint> attribs;
444 attribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
445 attribs.push_back(version / 10);
446 attribs.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
447 attribs.push_back(version % 10);
448 attribs.push_back(EGL_NONE);
450 // Create cloned context with shared context
451 mEglContext = eglCreateContext(mEglDisplay, configs[configId], mEglSharedContext, attribs.data());
454 // Pre-, Post- functions are being called from the callbacks
455 void GlViewPreInit( const Dali::RenderCallbackInput& input )
457 // This runs on DALi RenderThread!!!
459 // Bind the shared context in case of threaded rendering
460 if(mThread && !mEglContextBound)
462 // Store the shared context just once
463 if(!mEglSharedContext )
465 // Store the shared context returned by the drawable callback
466 mEglSharedContext = std::any_cast<EGLContext>(input.eglContext);
468 // Setup the EGL context
469 mEglDisplay = eglGetCurrentDisplay();
471 // switch to shared context in order to create shared GL resources
472 auto currentContext = eglGetCurrentContext();
474 // Retrieve current surfaces (read and draw)
475 mDrawSurface = eglGetCurrentSurface(EGL_DRAW);
476 mReadSurface = eglGetCurrentSurface(EGL_READ);
478 eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglSharedContext);
480 [[maybe_unused]] auto eglError = eglGetError();
482 // Now clone it to create compatible context for our threaded rendering
485 // Bring back current context
486 eglMakeCurrent(mEglDisplay, mDrawSurface, mReadSurface, currentContext);
490 GLuint CreateProgram(const char* vertexSource, const char* fragmentSource)
492 GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vertexSource);
497 GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fragmentSource);
502 GLuint program = glCreateProgram();
505 GL(glAttachShader(program, vertexShader));
506 GL(glAttachShader(program, fragmentShader));
507 GL(glLinkProgram(program));
508 GLint linkStatus = GL_FALSE;
509 GL(glGetProgramiv(program, GL_LINK_STATUS, &linkStatus));
510 if(linkStatus != GL_TRUE)
513 glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
516 char* buf = (char*)malloc(bufLength);
519 glGetProgramInfoLog(program, bufLength, NULL, buf);
523 glDeleteProgram(program);
530 GLuint LoadShader(GLenum shaderType, const char* shaderSource)
532 GLuint shader = glCreateShader(shaderType);
535 GL(glShaderSource(shader, 1, &shaderSource, NULL));
536 GL(glCompileShader(shader));
538 glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
539 if(compiled != GL_TRUE)
542 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
546 char* logBuffer = (char*)malloc(infoLen);
548 if(logBuffer != NULL)
550 glGetShaderInfoLog(shader, infoLen, NULL, logBuffer);
552 DALI_ASSERT_ALWAYS( true && logBuffer);
558 glDeleteShader(shader);
566 void PrepareBlitShader()
568 static const char glVertexShader[] =
569 "attribute vec2 vertexPosition;\n"
570 "attribute vec2 texCoords;\n"
571 "varying vec2 vTexCoords ;\n"
574 " gl_Position = vec4(vertexPosition, 0.0, 1.0);\n"
575 " vTexCoords = texCoords;\n"
578 static const char glFragmentShader[] =
579 "precision mediump float;\n"
580 "varying vec2 vTexCoords;\n"
581 "uniform sampler2D tex;\n"
584 " gl_FragColor = texture2D(tex, vTexCoords);\n"
587 mBlitProgram = CreateProgram(glVertexShader, glFragmentShader);
588 mBlitVertexLocation = glGetAttribLocation(mBlitProgram, "vertexPosition");
589 mBlitTexCoord = glGetAttribLocation(mBlitProgram, "texCoords");
592 GLuint mBlitProgram{0u};
593 GLuint mBlitVertexLocation{0u};
594 GLuint mBlitTexCoord{0u};
597 * Initializes FBO textures
599 void InitializeOffscreenFramebuffers()
601 for(auto i = 0u; i < mCreateInfo.maxOffscreenBuffers; ++i)
603 mFramebufferTexture.emplace_back();
604 mFramebufferTexture.back().textureId = CreateOffscreenTexture(mWidth, mHeight);
606 // Populate Draw queue entries
607 mTextureDrawQueue.push_back(i);
609 // Create framebuffers
610 CreateFramebuffer( i, mWidth, mHeight);
615 * Creates an offscreen texture for threaded renderer
617 uint32_t CreateOffscreenTexture(uint32_t width, uint32_t height)
619 GLuint offscreenTexture{0u};
620 GL(glGenTextures(1, &offscreenTexture));
621 GL(glBindTexture(GL_TEXTURE_2D, offscreenTexture));
622 GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
623 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
624 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
625 return offscreenTexture;
628 void GlViewInit(const Dali::RenderCallbackInput& input)
632 GlViewPreInit(input);
633 CallbackBase::Execute(*mOnInitCallback, input);
637 int GlViewRender(const Dali::RenderCallbackInput& input)
639 // Non-threaded solution invokes callback directly
641 if(!mCreateInfo.threadEnabled)
643 if(mOnRenderCallback)
645 retval = CallbackBase::ExecuteReturn<int>(*mOnRenderCallback, input);
655 void GlViewTerminate(const Dali::RenderCallbackInput& input)
657 // Non-threaded solution invokes callback directly
658 if(!mCreateInfo.threadEnabled)
660 if(mOnTerminateCallback)
662 CallbackBase::Execute(*mOnTerminateCallback, input);
669 // If no threaded mode, return
670 if(!mCreateInfo.threadEnabled)
681 // Deqeueue texture, there should be always something waiting to be drawn, if not, ignore
682 FrameBufferTexture fb;
683 auto textureBufferIndex = DequeueTextureReadBuffer(fb);
685 // Do nothing if frame not ready
686 if(textureBufferIndex < 0)
688 if(mLastTextureBufferIndex >= 0)
690 textureBufferIndex = mLastTextureBufferIndex;
699 // return last texture to the pull
700 if(mLastTextureBufferIndex >= 0)
702 // return it to the queue
703 EnqueueTextureDrawBuffer( mLastTextureBufferIndex );
707 GL(glViewport(x, y, w, h));
710 mBlitStateDone = true;
711 GL(glUseProgram(mBlitProgram));
712 GL(glVertexAttribPointer(mBlitVertexLocation, 2, GL_FLOAT, GL_FALSE, 0, QUAD_VERTS));
713 GL(glEnableVertexAttribArray(mBlitVertexLocation));
714 GL(glVertexAttribPointer(mBlitTexCoord, 2, GL_FLOAT, GL_FALSE, 0, QUAD_UV));
715 GL(glEnableVertexAttribArray(mBlitTexCoord));
716 GL(glActiveTexture(GL_TEXTURE0));
718 GL(glBindTexture(GL_TEXTURE_2D, mFramebufferTexture[textureBufferIndex].textureId));
720 GL(glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, QUAD_INDICES))
722 mLastTextureBufferIndex = textureBufferIndex;
725 // List of offscreen framebuffers
726 std::vector<FrameBufferTexture> mFramebufferTexture{};
729 * Rendering pipeline uses 3 queues:
730 * Draw - the producer queue (NativeRenderer thread writes to it)
731 * Read - the consumer queue (RenderThread reads from it)
732 * Stage - Already written but not ready to be read (not signaled)
734 * // Rendering offscreen
735 * 1. PRODUCER pops framebuffer from Draw queue
736 * 2. PRODUCER renders into the framebuffer
737 * 3. PRODUCER pushes framebuffer into Stage queue
739 * // Rendering onscreen
740 * 1. CONSUMER checks Stage queue for signaled (ready) framebuffers
741 * 2. If framebuffers are ready, pushes them into the Read queue
742 * 3. If MAILBOX mode, CONSUMER discards all 'outdated' framebuffers and displays the most recent
743 * 4. If FIFO mode, CONSUMER displays all the images in order of being produced.
745 std::deque<uint32_t> mTextureDrawQueue;
746 std::deque<uint32_t> mTextureStageQueue;
747 std::deque<uint32_t> mTextureReadQueue;
749 // Mutex guarding the queues reads/writes
750 std::recursive_mutex mTextureQueueMutex;
751 std::unique_ptr<std::thread> mThread; ///< Thread for parallel mode
752 bool mRunning{false}; ///< Thread running flag
753 EGLContext mEglContext{nullptr}; ///< EGL context associated with the thread
754 EGLContext mEglSharedContext{nullptr};
756 EGLDisplay mEglDisplay{nullptr}; ///< Current EGL display
757 std::atomic_bool mEglContextBound{false}; ///< Flag indicating whether EGL context is bound
758 EGLSurface mDrawSurface{EGL_NO_SURFACE}; ///< Current EGL draw surface
759 EGLSurface mReadSurface{EGL_NO_SURFACE}; ///< Current EGL read surface
761 // Callbacks associated with GlView interface
762 std::unique_ptr<CallbackBase> mOnInitCallback{nullptr};
763 std::unique_ptr<CallbackBase> mOnRenderCallback{nullptr};
764 std::unique_ptr<CallbackBase> mOnTerminateCallback{nullptr};
766 int32_t mLastTextureBufferIndex{-1};
767 bool mBlitStateDone{false};
769 std::mutex mRenderCallbackInputDataMutex{};
770 Dali::RenderCallbackInput mRenderCallbackInputData{};
772 NativeRendererCreateInfo mCreateInfo{};
775 DrawableViewNativeRenderer::DrawableViewNativeRenderer(const NativeRendererCreateInfo& createInfo)
776 : mImpl(new Impl(createInfo))
778 if(createInfo.threadEnabled)
780 mImpl->InitializeThread();
784 DrawableViewNativeRenderer::~DrawableViewNativeRenderer() = default;
786 void DrawableViewNativeRenderer::RegisterGlCallbacks(Dali::CallbackBase* onInitCallback, Dali::CallbackBase* onRenderCallback, Dali::CallbackBase* onTerminateCallback)
788 mImpl->mOnInitCallback.reset(onInitCallback);
789 mImpl->mOnRenderCallback.reset(onRenderCallback);
790 mImpl->mOnTerminateCallback.reset(onTerminateCallback);
793 void DrawableViewNativeRenderer::InvokeGlInitCallback(const RenderCallbackInput& renderCallbackInput)
795 mImpl->GlViewInit(renderCallbackInput);
798 void DrawableViewNativeRenderer::InvokeGlRenderCallback(const RenderCallbackInput& renderCallbackInput)
800 mImpl->GlViewRender(renderCallbackInput);
803 void DrawableViewNativeRenderer::InvokeGlTerminateCallback(const RenderCallbackInput& renderCallbackInput)
805 mImpl->GlViewTerminate(renderCallbackInput);
808 void DrawableViewNativeRenderer::Resize(uint32_t width, uint32_t height)
810 mImpl->Resize(width, height);
813 void DrawableViewNativeRenderer::PushRenderCallbackInputData( const Dali::RenderCallbackInput& renderCallbackInput )
815 mImpl->PushRenderCallbackInputData(renderCallbackInput);
818 void DrawableViewNativeRenderer::Terminate()
823 } // namespace Dali::Internal