2 * Copyright (c) 2023 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!
28 #include <EGL/eglext.h>
29 #include <GLES3/gl3.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
53 constexpr float QUAD_VERTS[] = {
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 1.0f, 1.0f, // top right
73 1.0f, 0.0f, // bottom right
74 0.0f, 0.0f, // bottom left
75 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;
254 explicit Impl(const NativeRendererCreateInfo& renderCreateInfo)
255 : mCreateInfo(renderCreateInfo)
272 * Initializes renderer thread for offscreen rendering
274 void InitializeThread()
276 // Make mRunning true first
277 // Terminate() may be called before StartThread()
280 mThread = std::make_unique<std::thread>(&Impl::StartThread, this);
283 void PushRenderCallbackInputData(const Dali::RenderCallbackInput& renderCallbackInput)
285 std::scoped_lock<std::mutex> lock(mRenderCallbackInputDataMutex);
286 mRenderCallbackInputData = renderCallbackInput;
289 void PopRenderCallbackInputData(Dali::RenderCallbackInput& renderCallbackInput)
291 std::scoped_lock<std::mutex> lock(mRenderCallbackInputDataMutex);
292 renderCallbackInput = mRenderCallbackInputData;
301 * Function initializes thread for parallel rendering.
303 * 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))
388 if(mOnRenderCallback)
390 CallbackBase::ExecuteReturn<int>(*mOnRenderCallback);
393 // If the framebuffer is guarded with fence object then
394 // delete it as at this point it is no longer valid.
397 // Make sure GPU finished
398 glDeleteSync(fb.fence);
402 // Inject sync object into the GL commands stream
403 fb.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
404 EnqueueTextureReadBuffer(index);
405 glBindFramebuffer(GL_FRAMEBUFFER, 0);
409 void Resize(uint32_t width, uint32_t height)
413 mResizeRequest = true;
417 uint32_t mHeight{0u};
418 std::atomic_bool mResizeRequest{false};
421 * Clones current EGL context, this function must be called from the render callback
422 * and be executed on the DALi RenderThread
424 void CloneEglContext()
426 // extract shared context (void*)
427 auto context = eglGetCurrentContext();
432 eglGetConfigs(mEglDisplay, nullptr, 0, &size);
433 std::vector<EGLConfig> configs;
434 configs.resize(size);
435 eglGetConfigs(mEglDisplay, configs.data(), EGLint(configs.size()), &size);
437 // Find out which config is used by current context
438 eglQueryContext(mEglDisplay, context, EGL_CONFIG_ID, &configId);
441 auto version = int(30); // TODO: get context version and select the same one
442 std::vector<EGLint> attribs;
443 attribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
444 attribs.push_back(version / 10);
445 attribs.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
446 attribs.push_back(version % 10);
447 attribs.push_back(EGL_NONE);
449 // Create cloned context with shared context
450 mEglContext = eglCreateContext(mEglDisplay, configs[configId], mEglSharedContext, attribs.data());
453 // Pre-, Post- functions are being called from the callbacks
454 void GlViewPreInit(const Dali::RenderCallbackInput& input)
456 // This runs on DALi RenderThread!!!
458 // Bind the shared context in case of threaded rendering
459 if(mThread && !mEglContextBound)
461 // Store the shared context just once
462 if(!mEglSharedContext)
464 // Store the shared context returned by the drawable callback
465 mEglSharedContext = std::any_cast<EGLContext>(input.eglContext);
467 // Setup the EGL context
468 mEglDisplay = eglGetCurrentDisplay();
470 // switch to shared context in order to create shared GL resources
471 auto currentContext = eglGetCurrentContext();
473 // Retrieve current surfaces (read and draw)
474 mDrawSurface = eglGetCurrentSurface(EGL_DRAW);
475 mReadSurface = eglGetCurrentSurface(EGL_READ);
477 eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglSharedContext);
479 [[maybe_unused]] auto eglError = eglGetError();
481 // Now clone it to create compatible context for our threaded rendering
484 // Bring back current context
485 eglMakeCurrent(mEglDisplay, mDrawSurface, mReadSurface, currentContext);
489 GLuint CreateProgram(const char* vertexSource, const char* fragmentSource)
491 GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vertexSource);
496 GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fragmentSource);
501 GLuint program = glCreateProgram();
504 GL(glAttachShader(program, vertexShader));
505 GL(glAttachShader(program, fragmentShader));
506 GL(glLinkProgram(program));
507 GLint linkStatus = GL_FALSE;
508 GL(glGetProgramiv(program, GL_LINK_STATUS, &linkStatus));
509 if(linkStatus != GL_TRUE)
512 glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
515 char* buf = (char*)malloc(bufLength);
518 glGetProgramInfoLog(program, bufLength, NULL, buf);
522 glDeleteProgram(program);
529 GLuint LoadShader(GLenum shaderType, const char* shaderSource)
531 GLuint shader = glCreateShader(shaderType);
534 GL(glShaderSource(shader, 1, &shaderSource, NULL));
535 GL(glCompileShader(shader));
537 glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
538 if(compiled != GL_TRUE)
541 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
545 char* logBuffer = (char*)malloc(infoLen);
547 if(logBuffer != NULL)
549 glGetShaderInfoLog(shader, infoLen, NULL, logBuffer);
551 DALI_ASSERT_ALWAYS(true && logBuffer);
557 glDeleteShader(shader);
565 void PrepareBlitShader()
567 static const char glVertexShader[] =
568 "attribute vec2 vertexPosition;\n"
569 "attribute vec2 texCoords;\n"
570 "varying vec2 vTexCoords ;\n"
573 " gl_Position = vec4(vertexPosition, 0.0, 1.0);\n"
574 " vTexCoords = texCoords;\n"
577 static const char glFragmentShader[] =
578 "precision mediump float;\n"
579 "varying vec2 vTexCoords;\n"
580 "uniform sampler2D tex;\n"
583 " gl_FragColor = texture2D(tex, vTexCoords);\n"
586 mBlitProgram = CreateProgram(glVertexShader, glFragmentShader);
587 mBlitVertexLocation = glGetAttribLocation(mBlitProgram, "vertexPosition");
588 mBlitTexCoord = glGetAttribLocation(mBlitProgram, "texCoords");
591 GLuint mBlitProgram{0u};
592 GLuint mBlitVertexLocation{0u};
593 GLuint mBlitTexCoord{0u};
596 * Initializes FBO textures
598 void InitializeOffscreenFramebuffers()
600 for(auto i = 0u; i < mCreateInfo.maxOffscreenBuffers; ++i)
602 mFramebufferTexture.emplace_back();
603 mFramebufferTexture.back().textureId = CreateOffscreenTexture(mWidth, mHeight);
605 // Populate Draw queue entries
606 mTextureDrawQueue.push_back(i);
608 // Create framebuffers
609 CreateFramebuffer(i, mWidth, mHeight);
614 * Creates an offscreen texture for threaded renderer
616 uint32_t CreateOffscreenTexture(uint32_t width, uint32_t height)
618 GLuint offscreenTexture{0u};
619 GL(glGenTextures(1, &offscreenTexture));
620 GL(glBindTexture(GL_TEXTURE_2D, offscreenTexture));
621 GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
622 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
623 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
624 return offscreenTexture;
627 void GlViewInit(const Dali::RenderCallbackInput& input)
631 GlViewPreInit(input);
632 CallbackBase::Execute(*mOnInitCallback, input);
636 int GlViewRender(const Dali::RenderCallbackInput& input)
638 // Non-threaded solution invokes callback directly
640 if(!mCreateInfo.threadEnabled)
642 if(mOnRenderCallback)
644 retval = CallbackBase::ExecuteReturn<int>(*mOnRenderCallback, input);
654 void GlViewTerminate(const Dali::RenderCallbackInput& input)
656 // Non-threaded solution invokes callback directly
657 if(!mCreateInfo.threadEnabled)
659 if(mOnTerminateCallback)
661 CallbackBase::Execute(*mOnTerminateCallback, input);
668 // If no threaded mode, return
669 if(!mCreateInfo.threadEnabled)
680 // Deqeueue texture, there should be always something waiting to be drawn, if not, ignore
681 FrameBufferTexture fb;
682 auto textureBufferIndex = DequeueTextureReadBuffer(fb);
684 // Do nothing if frame not ready
685 if(textureBufferIndex < 0)
687 if(mLastTextureBufferIndex >= 0)
689 textureBufferIndex = mLastTextureBufferIndex;
698 // return last texture to the pull
699 if(mLastTextureBufferIndex >= 0)
701 // return it to the queue
702 EnqueueTextureDrawBuffer(mLastTextureBufferIndex);
706 GL(glViewport(x, y, w, h));
709 mBlitStateDone = true;
710 GL(glUseProgram(mBlitProgram));
711 GL(glVertexAttribPointer(mBlitVertexLocation, 2, GL_FLOAT, GL_FALSE, 0, QUAD_VERTS));
712 GL(glEnableVertexAttribArray(mBlitVertexLocation));
713 GL(glVertexAttribPointer(mBlitTexCoord, 2, GL_FLOAT, GL_FALSE, 0, QUAD_UV));
714 GL(glEnableVertexAttribArray(mBlitTexCoord));
715 GL(glActiveTexture(GL_TEXTURE0));
717 GL(glBindTexture(GL_TEXTURE_2D, mFramebufferTexture[textureBufferIndex].textureId));
719 GL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, QUAD_INDICES))
721 mLastTextureBufferIndex = textureBufferIndex;
724 // List of offscreen framebuffers
725 std::vector<FrameBufferTexture> mFramebufferTexture{};
728 * Rendering pipeline uses 3 queues:
729 * Draw - the producer queue (NativeRenderer thread writes to it)
730 * Read - the consumer queue (RenderThread reads from it)
731 * Stage - Already written but not ready to be read (not signaled)
733 * // Rendering offscreen
734 * 1. PRODUCER pops framebuffer from Draw queue
735 * 2. PRODUCER renders into the framebuffer
736 * 3. PRODUCER pushes framebuffer into Stage queue
738 * // Rendering onscreen
739 * 1. CONSUMER checks Stage queue for signaled (ready) framebuffers
740 * 2. If framebuffers are ready, pushes them into the Read queue
741 * 3. If MAILBOX mode, CONSUMER discards all 'outdated' framebuffers and displays the most recent
742 * 4. If FIFO mode, CONSUMER displays all the images in order of being produced.
744 std::deque<uint32_t> mTextureDrawQueue;
745 std::deque<uint32_t> mTextureStageQueue;
746 std::deque<uint32_t> mTextureReadQueue;
748 // Mutex guarding the queues reads/writes
749 std::recursive_mutex mTextureQueueMutex;
750 std::unique_ptr<std::thread> mThread; ///< Thread for parallel mode
751 bool mRunning{false}; ///< Thread running flag
752 EGLContext mEglContext{nullptr}; ///< EGL context associated with the thread
753 EGLContext mEglSharedContext{nullptr};
755 EGLDisplay mEglDisplay{nullptr}; ///< Current EGL display
756 std::atomic_bool mEglContextBound{false}; ///< Flag indicating whether EGL context is bound
757 EGLSurface mDrawSurface{EGL_NO_SURFACE}; ///< Current EGL draw surface
758 EGLSurface mReadSurface{EGL_NO_SURFACE}; ///< Current EGL read surface
760 // Callbacks associated with GlView interface
761 std::unique_ptr<CallbackBase> mOnInitCallback{nullptr};
762 std::unique_ptr<CallbackBase> mOnRenderCallback{nullptr};
763 std::unique_ptr<CallbackBase> mOnTerminateCallback{nullptr};
765 int32_t mLastTextureBufferIndex{-1};
766 bool mBlitStateDone{false};
768 std::mutex mRenderCallbackInputDataMutex{};
769 Dali::RenderCallbackInput mRenderCallbackInputData{};
771 NativeRendererCreateInfo mCreateInfo{};
774 DrawableViewNativeRenderer::DrawableViewNativeRenderer(const NativeRendererCreateInfo& createInfo)
775 : mImpl(new Impl(createInfo))
777 if(createInfo.threadEnabled)
779 mImpl->InitializeThread();
783 DrawableViewNativeRenderer::~DrawableViewNativeRenderer() = default;
785 void DrawableViewNativeRenderer::RegisterGlCallbacks(Dali::CallbackBase* onInitCallback, Dali::CallbackBase* onRenderCallback, Dali::CallbackBase* onTerminateCallback)
787 mImpl->mOnInitCallback.reset(onInitCallback);
788 mImpl->mOnRenderCallback.reset(onRenderCallback);
789 mImpl->mOnTerminateCallback.reset(onTerminateCallback);
792 void DrawableViewNativeRenderer::InvokeGlInitCallback(const RenderCallbackInput& renderCallbackInput)
794 mImpl->GlViewInit(renderCallbackInput);
797 void DrawableViewNativeRenderer::InvokeGlRenderCallback(const RenderCallbackInput& renderCallbackInput)
799 mImpl->GlViewRender(renderCallbackInput);
802 void DrawableViewNativeRenderer::InvokeGlTerminateCallback(const RenderCallbackInput& renderCallbackInput)
804 mImpl->GlViewTerminate(renderCallbackInput);
807 void DrawableViewNativeRenderer::Resize(uint32_t width, uint32_t height)
809 mImpl->Resize(width, height);
812 void DrawableViewNativeRenderer::PushRenderCallbackInputData(const Dali::RenderCallbackInput& renderCallbackInput)
814 mImpl->PushRenderCallbackInputData(renderCallbackInput);
817 void DrawableViewNativeRenderer::Terminate()
822 } // namespace Dali::Internal