/* * Copyright (C) 2024 Amlogic Corporation. * * 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 #include "MesonDisplay.h" #include "V2DComposer.h" #include "DisplayThread.h" #include "MultiplanesComposition.h" #include "DispAdptLog.h" #include "MesonEventListener.h" #include "VtAllocSolidColorBuffer.h" #include "VideoTunnelDev.h" MesonDisplay::MesonDisplay(uint32_t display) { mDisplayId = display; mDisplayToMainPlane = false; mEnablePrimaryPlane = true; mDelayLatency = 0; mVsyncTimestamp = 0; mLayerSeq = 0; mPipe = std::make_shared(display); mProcessorsManager = std::make_shared( (display == 0) ? true : false); mPipeAdapter = std::make_shared(); } MesonDisplay::~MesonDisplay() { DISPLAY_LOGD("%s[%d]", __func__, __LINE__); if (mPipe.get()) mPipe->destroyThread(); if (mDisplayThread.get()) mDisplayThread->destroyThread(); if (mVsync.get()) { mVsync->setEnabled(false); mVsync->setObserver(NULL); mVsync.reset(); } mLayers.clear(); for (auto planeIt = mPlanes.begin(); planeIt != mPlanes.end(); ++planeIt) (*planeIt)->setPlane(NULL, 0, BLANK_FOR_NO_CONTENT); mPlanes.clear(); mComposers.clear(); mCompositionStrategy.reset(); mPresentCompositionStg.reset(); mProcessorsManager.reset(); mPipe.reset(); mPipeAdapter.reset(); if (mListener.get()) mListener.reset(); VtAllocSolidColorBuffer::resetInstance(); VideoTunnelDev::resetInstanceForTest(); } int32_t MesonDisplay::initialize() { std::vector> composers; const char *env = getenv("DISPLAYADAPTER_ENABLE_PRIMARY_PLANE"); if (env && atoi(env) != 1) mEnablePrimaryPlane = false; getComposers(composers); for (auto it = composers.begin(); it != composers.end(); it++) { mComposers.emplace(2, std::move(*it)); } if (mPipe.get()) { if (mEnablePrimaryPlane) mPipe->enablePrimaryPlane(); mPipe->getCrtc(mCrtc); mPipe->getConnector(mConnector); mPipe->getPlanes(mPlanes); for (auto & plane : mPlanes) { plane->setCrtcId(mCrtc->getId()); } } mListener = std::make_shared(this, ADPT_LISTENER_DIR); mListener->registerHandler(IN_CLOSE_WRITE | IN_CREATE | IN_DELETE); mListener->addWatch(ADPT_VIDEO, [this](const std::string& path, int mask) { char data[STR_LEN] = { 0 }; int state = 0; if (mask & IN_CLOSE_WRITE) { DispAdptReadFile(path.c_str(), data, true, STR_LEN); if (sscanf(data, "%d", &state) == 1) this->setVideoPlaneState(state); } }); mListener->addWatch(DEBUG_LOG, [this](const std::string& path, int mask) { char data[STR_LEN] = { 0 }; if (mask & IN_CLOSE_WRITE) { DispAdptReadFile(path.c_str(), data, true, STR_LEN); DispAdptDebugSetting(data); } }); mCompositionStrategy = std::make_shared(); std::shared_ptr crtc = std::static_pointer_cast(mCrtc); std::shared_ptr mesonVsync = std::make_shared(DISPLAY_DEFAULT); mesonVsync->setVsyncMode(VSYNC_MODE_HW, crtc); setVsync(mesonVsync); if (!mDisplayThread.get()) { mDisplayThread = std::make_shared(this); } mesonVsync->setEnabled(true); initLayerIdGenerator(); return 0; } int32_t MesonDisplay::createV2DComposer(int idx, std::shared_ptr & composer) { int ret = 0; int fd = -1; char path[64] = { 0 }; snprintf(path, 64, "/dev/v2d.%u", idx); fd = open(path, O_RDWR, 0); if (fd >= 0) { DISPLAY_LOGD("create V2DComposer for %s succeed", path); composer = std::make_shared(mDisplayId, fd); } else { DISPLAY_LOGE("create V2DComposer for %s failed: %s", path, strerror(errno)); ret = -1; } if (fd >= 0) close(fd); return ret; } void MesonDisplay::getComposers(std::vector> & composers) { int idx = 0; int ret; std::shared_ptr composer; while (true) { char path[64]; snprintf(path, 64, "/dev/v2d.%u", idx); if (access(path, R_OK | W_OK) == 0) idx ++; else break; } int id = mEnablePrimaryPlane ? 0 : 1; composers.clear(); for (auto i = id; i < idx; i++) { ret = createV2DComposer(i, composer); if (ret) return; else composers.push_back(std::move(composer)); } } /* LayerId is 16bits. Higher 8 Bits: now is 256 layer slot, the higher 8 bits is the slot index. Lower 8 Bits: a sequence no, used to distinguish layers have same slot index ( which may happened a layer destroyed and at the same time a new layer created.) */ #define MAX_MESON_LAYERS (256) #define LAYER_SLOT_BITS (8) void MesonDisplay::initLayerIdGenerator() { mLayersBitmap = std::make_shared(MAX_MESON_LAYERS); mLayerSeq = 0; } uint32_t MesonDisplay::createLayerId() { uint32_t layerId = 0; int idx = mLayersBitmap->getZeroBit(); mLayersBitmap->setBit(idx); mLayerSeq++; mLayerSeq %= MAX_MESON_LAYERS; layerId = ((idx & (MAX_MESON_LAYERS - 1)) << LAYER_SLOT_BITS) | mLayerSeq; return layerId; } void MesonDisplay::destroyLayerId(uint32_t id) { int slotIdx = id >> LAYER_SLOT_BITS; mLayersBitmap->clearBit(slotIdx); } void MesonDisplay::registerCallback(int32_t layerId, void *context, meson_callback_t *cb) { std::lock_guard lock(mMutex); auto layerIt = mLayers.find(layerId); if (layerIt != mLayers.end()) layerIt->second->registerCallback(context, cb); } void MesonDisplay::registerPageFlipCallBack(int32_t layerId, void *context, adpt_page_flip_callback *cb) { std::lock_guard lock(mMutex); auto layerIt = mLayers.find(layerId); if (layerIt != mLayers.end()) layerIt->second->registerPageFlipCallBack(context, cb); if (mPipe.get()) mPipe->drmPageFlipInit(); } int MesonDisplay::createLayer(int32_t &outLayer) { std::lock_guard lock(mMutex); std::shared_ptr layer = std::make_shared(mDisplayId); uint32_t idx = createLayerId(); outLayer = idx; layer->setUniqueId(outLayer); mLayers.emplace(outLayer, layer); return MESON_ERROR_NONE; } int MesonDisplay::destroyLayer(int32_t inLayer) { std::lock_guard lock(mMutex); auto layerit = mLayers.find(inLayer); if (layerit == mLayers.end()) return MESON_ERROR_BAD_LAYER; std::shared_ptr layer = layerit->second; mLayers.erase(inLayer); destroyLayerId(inLayer); DISPLAY_LOGD("%s[%d]: layer: %d", __func__, __LINE__, inLayer); if (layer.get() && !layer->isVtBuffer()) layer->releaseResource(); if (layer.get() && layer->isVtBuffer()) layer->releaseVtResource(); if (mPipe.get()) mPipe->destroyPageFlip(inLayer); if (layer->keepFrame()) { for (auto planeIt = mPlanes.begin(); planeIt != mPlanes.end(); ++planeIt) (*planeIt)->setKeepFrame(); return MESON_ERROR_HAS_CHANGES; } return MESON_ERROR_NONE; } int32_t MesonDisplay::setTunnelId(int32_t layer, int32_t tunnelId) { std::lock_guard lock(mMutex); std::shared_ptr observer = std::make_shared(this); if (tunnelId < 128 && tunnelId >= 0) VtAllocSolidColorBuffer::getInstance().allocBuffer(); auto layerIt = mLayers.find(layer); if (layerIt != mLayers.end()) return layerIt->second->setTunnelId(tunnelId, std::move(observer)); return -1; } int32_t MesonDisplay::setLayerIndex(int32_t layer, int32_t index) { std::lock_guard lock(mMutex); auto layerIt = mLayers.find(layer); if (layerIt != mLayers.end()) { if (index < MAX_LAYER_NUMBER && !(mState & (1 << index))) layerIt->second->setVisible(false); return layerIt->second->setIndex(index); } return -1; } int32_t MesonDisplay::setLayerZorder(int32_t layer, int32_t zorder) { std::lock_guard lock(mMutex); auto layerIt = mLayers.find(layer); if (layerIt != mLayers.end()) return layerIt->second->setZorder(zorder); return -1; } int32_t MesonDisplay::setLayerSourceCrop(int32_t layerId, struct rect crop) { std::lock_guard lock(mMutex); drm_rect_t layer_crop; auto layerIt = mLayers.find(layerId); layer_crop.left = crop.x; layer_crop.top = crop.y; layer_crop.right = crop.x + crop.w; layer_crop.bottom = crop.y + crop.h; if (layerIt != mLayers.end()) return layerIt->second->setSourceCrop(layer_crop); return -1; } int32_t MesonDisplay::setLayerDispFrame(int32_t layer, drm_rect_t frame) { std::lock_guard lock(mMutex); auto layerIt = mLayers.find(layer); if (layerIt != mLayers.end()) { int32_t displayW = 0; int32_t displayH = 0; mPipe->getSize(displayW, displayH); if (displayW > 0 && displayH > 0) { float scaleW = (float)displayW / DISPLAY_WIDTH_BASE; float scaleH = (float)displayH / DISPLAY_HEIGHT_BASE; frame.left = frame.left * scaleW; frame.top = frame.top * scaleH; frame.right = frame.right * scaleW; frame.bottom = frame.bottom * scaleH; if (frame.right > displayW) frame.right = displayW; if (frame.bottom > displayH) frame.bottom = displayH; } return layerIt->second->setDisplayFrame(frame); } return -1; } void MesonDisplay::setVsyncDelayLatency(int32_t latency) { mDelayLatency = latency; } int32_t MesonDisplay::getVsyncDelayLatency(int32_t layer) { return mDelayLatency; } void MesonDisplay::setVideoPlaneState(int state) { std::lock_guard lock(mMutex); std::string value = VIDEO_ENABLE; bool visible = true; int index = -1; int change = mState != -1 ? mState ^ state : 0xf; if (change != 0) { if (state == 0) { value = VIDEO_DISABLE; } if (mDisableValue != value) DispAdptWriteFile(DISABLE_VIDEO, value.c_str()); mDisableValue = std::move(value); for (auto it = mLayers.begin(); it != mLayers.end(); it++) { index = it->second->getIndex(); if (index < 0 || index >= MAX_LAYER_NUMBER) continue; if ((change & (1U << BASE_SHIFT_LEFT(index))) == 0) continue; visible = false; if (state & (1U << BASE_SHIFT_LEFT(index))) visible = true; it->second->setVisible(visible); } } mState = state; DISPLAY_LOGV("%s[%d]: state: 0x%x, change: 0x%x", __func__, __LINE__, state, change); } int32_t MesonDisplay::getUiSize(int32_t* width, int32_t* height) { if (width == nullptr || height == nullptr) { DISPLAY_LOGE("Invalid pointer: width or height is null"); return -1; } *width = DISPLAY_WIDTH_BASE; *height = DISPLAY_HEIGHT_BASE; return 0; } int32_t MesonDisplay::setVideoDisplaytoMainplane() { char path_select[MESON_MAX_STR_LEN] = { 0 }; int vd1, vd2, vd3; int ret; if (mEnablePrimaryPlane) return 0; ret = DispAdptReadFile(VIDEO_PATH_SELECT, path_select, true, MESON_MAX_STR_LEN); if (ret) { DISPLAY_LOGE("read file(%s) fail", VIDEO_PATH_SELECT); return ret; } if (sscanf(path_select, "vd1: %d vd2: %d, vd3: %d", &vd1, &vd2, &vd3) == 3) { memset(path_select, 0, sizeof(path_select)); sprintf(path_select, "%d %d %d", vd2, vd1, vd3); DispAdptWriteFile(VIDEO_PATH_SELECT, path_select); DispAdptWriteFile(DISABLE_VIDEO, "0"); DispAdptWriteFile(VIDEO_TEST_PATTERN, "0 1 0xff"); DispAdptWriteFile(VIDEO_TEST_PATTERN, "0 0 0xff"); } return 0; } int32_t MesonDisplay::setLayerTransform(int32_t layer, int32_t transform) { std::lock_guard lock(mMutex); auto layerIt = mLayers.find(layer); if (layerIt != mLayers.end()) { switch (transform) { case ROTATION_90: transform = TRANSFORM_ROT_90; break; case ROTATION_270: transform = TRANSFORM_ROT_270; break; case ROTATION_180: transform = TRANSFORM_ROT_180; break; default: transform = TRANSFORM_NONE; break; } return layerIt->second->setTransform(transform); } return -1; } int32_t MesonDisplay::getVideoDelay(int fps) { if (mPipe.get() && mPipeAdapter.get()) { int refresh = mPipe->getVrefresh(); int vsync_time = refresh > 0 ? 1000 / refresh : 0; int drm_delay = 3 * vsync_time; int video_delay = mPipeAdapter->getVideoDelay(fps, refresh * 1000); if (video_delay < 0) return -1; if (video_delay > std::numeric_limits::max() - drm_delay) return -1; return video_delay + drm_delay; } return -1; } int32_t MesonDisplay::waitVBlank() { if (!mCrtc.get()) { return -EFAULT; } std::shared_ptr localCrtc = mCrtc; nsecs_t vsyncTime = 0; int ret = localCrtc->waitVBlank(vsyncTime); localCrtc.reset(); return ret; } int MesonDisplay::getLayerTotal() { std::lock_guard lock(mMutex); return mLayers.size(); } int32_t MesonDisplay::setVsync(std::shared_ptr vsync) { std::lock_guard lock(mMutex); int vsyncType = vsync->getVsyncType(); switch (vsyncType) { case DISPLAY_DEFAULT: if (mVsync != vsync) { if (mVsync.get()) { mVsync->setEnabled(false); mVsync->setObserver(NULL); } else { mVsync = std::move(vsync); mVsync->setObserver(this); } } break; default: DISPLAY_LOGE("Get invalid vsync"); break; } return 0; } void MesonDisplay::onVsync(int64_t timestamp, uint32_t vsyncPeriodNanos, int vsyncType) { switch (vsyncType) { case DISPLAY_DEFAULT: mVsyncTimestamp = timestamp; if (mDisplayThread.get()) { mDisplayThread->onVsync(timestamp, vsyncPeriodNanos); } else { DISPLAY_LOGE("MesonDisplay (%p) mDisplayThread is NULL", this); } break; default: DISPLAY_LOGE("onVsync get invalid vsync"); break; } } void MesonDisplay::setLayersPresentTime() { std::lock_guard vtLock(mMutex); nsecs_t period = 1e9 / 60; nsecs_t expectPresentedTime = mVsyncTimestamp + period; for (auto it = mLayers.begin(); it != mLayers.end(); it++) { // expectPresent time is current vsync timestamp + 1 vsyncPeriod it->second->setPresentTime(expectPresentedTime); } } int MesonDisplay::collectLayersForPresent() { /* * 1) add reference to Layers to keep it alive during display. * 2) for special layer, update its composition type. * 3) sort Layers by zorder for composition. */ mPresentLayers.reserve(10); for (auto it = mLayers.begin(); it != mLayers.end(); it++) { mPresentLayers.push_back(it->second); mFbs.insert(make_pair(it->second->getCurZorder(), it->second)); } return MESON_ERROR_NONE; } int MesonDisplay::collectPlanesForPresent() { mPresentPlanes = mPlanes; return MESON_ERROR_NONE; } int MesonDisplay::collectComposersForPresent() { for (auto it = mComposers.begin(); it != mComposers.end(); it++) { mPresentComposers.push_back(it->second); } return MESON_ERROR_NONE; } int MesonDisplay::collectCompositionStgForPresent() { if (!mCompositionStrategy.get()) return MESON_ERROR_BAD_PARAMETER; mPresentCompositionStg = mCompositionStrategy; return MESON_ERROR_NONE; } int MesonDisplay::validateDisplay(uint32_t* outNumTypes, uint32_t* outNumRequests) { std::lock_guard lock(mMutex); bool updateZ = true; mPresentLayers.clear(); mPresentComposers.clear(); mPresentPlanes.clear(); mPresentCompositionStg.reset(); mFbs.clear(); int ret = collectLayersForPresent(); if (ret != MESON_ERROR_NONE) { return ret; } ret = collectComposersForPresent(); if (ret != MESON_ERROR_NONE) { return ret; } ret = collectPlanesForPresent(); if (ret != MESON_ERROR_NONE) { return ret; } ret = collectCompositionStgForPresent(); if (ret != MESON_ERROR_NONE) { return ret; } if (mFbs.size() != mLayers.size()) updateZ = false; /*setup composition strategy.*/ std::vector> fbs; auto fbIt = mPresentLayers.begin(); for (; fbIt != mPresentLayers.end(); fbIt++) { if (updateZ) (*fbIt)->updateZorder(); fbs.push_back(std::static_pointer_cast(*fbIt)); } std::vector> planes; auto planeIt = mPresentPlanes.begin(); for (; planeIt != mPresentPlanes.end(); planeIt++) { if ((*planeIt)->getPrimary() || !mDisplayToMainPlane) planes.push_back(std::static_pointer_cast(*planeIt)); } std::list prevDisplayPairs; std::list currDisplayPairs; mPresentCompositionStg->getDisplayPair(prevDisplayPairs); std::shared_ptr crtc = std::static_pointer_cast(mCrtc); mPresentCompositionStg->setup(fbs, mPresentComposers, planes, crtc, 0, 0, { 0 }); if (mPresentCompositionStg->decideComposition() < 0) return MESON_ERROR_NO_RESOURCES; mPresentCompositionStg->getDisplayPair(currDisplayPairs); if (currDisplayPairs != prevDisplayPairs) { for (auto displayIt = currDisplayPairs.begin(); displayIt != currDisplayPairs.end(); ++displayIt) { std::shared_ptr fb = std::static_pointer_cast(*(displayIt->fbs.begin())); fb->froceRefresh(true); } } if (mProcessorsManager.get()) { std::list displayPairs; mPresentCompositionStg->getDisplayPair(displayPairs); mProcessorsManager->setup(std::move(displayPairs), 0); } return ret; } int MesonDisplay::presentDisplay(int32_t* outPresentFence) { int ret; std::lock_guard lock(mMutex); bool mainThread = !hasVtBuffer(); if (mPresentCompositionStg.get() == NULL) return MESON_ERROR_BAD_PARAMETER; if (mProcessorsManager.get()) mProcessorsManager->runVideoProcessors(mainThread); ret = mPresentCompositionStg->commit(); if (mProcessorsManager.get()) { mProcessorsManager->setVideoProcessorReleaseFence(ret != 0, mainThread); mProcessorsManager->restoretProcessorFromFreeToUnused(); } return ret; } bool MesonDisplay::handleDisplayConnection() { std::lock_guard vtLock(mMutex); std::shared_ptr layer; bool ret = false; for (auto it = mLayers.begin(); it != mLayers.end(); it++) { layer = it->second; if (!layer->isVtBuffer()) layer->updateBuffer(); else layer->updateVtBuffer(); ret = true; } return ret; } void MesonDisplay::releaseLayers() { std::lock_guard vtLock(mMutex); int ret; std::shared_ptr layer; for (auto it = mLayers.begin(); it != mLayers.end(); it++) { layer = it->second; if (!layer->isVtBuffer()) { ret = layer->releaseBuffer(); } else { ret = layer->releaseVtBuffer(); } if (ret != 0 && ret != -EAGAIN) { DISPLAY_LOGE("%s, release layer id=%" PRIu64 " failed, ret=%d", __func__, layer->getUniqueId(), ret); } } } /* * whether the videotunnel layer has new game buffer */ bool MesonDisplay::newGameBuffer() { std::lock_guard vtLock(mMutex); for (auto it = mLayers.begin(); it != mLayers.end(); it++) { auto layer = it->second; if (layer && layer->newGameBuffer()) return true; } return false; } void MesonDisplay::onFrameAvailable() { if (mDisplayThread.get()) mDisplayThread->onFrameAvailableForGameMode(); } bool MesonDisplay::hasVtBuffer() { for (auto it = mLayers.begin(); it != mLayers.end(); it++) { auto layer = it->second; if (layer && layer->isVtBuffer()) return true; } return false; }