diff --git a/display/hal/BUILD.gn b/display/hal/BUILD.gn index e283d08fc48e7d3afdd4c7f8c967c6c87514f389..cfc836de75feb9b7875ad726a9af2d8acb9c6549 100755 --- a/display/hal/BUILD.gn +++ b/display/hal/BUILD.gn @@ -11,88 +11,75 @@ # See the License for the specific language governing permissions and # limitations under the License. -if (defined(ohos_lite)) { - group("hdi_display") { - public_deps = [ "$ohos_board_adapter_dir/display:hdi_display" ] - } - group("hdi_display_device") { - deps = [] - public_configs = [] - } - group("hdi_display_gralloc") { - deps = [] - public_configs = [] - } -} else { - import("//build/ohos.gni") - import("//vendor/${product_company}/${product_name}/product.gni") +import("//build/gn/fangtian.gni") +# import("//vendor/${product_company}/${product_name}/product.gni") - config("display_hdi_public_config") { - visibility = [ ":*" ] - include_dirs = [ - "//drivers/peripheral/base", - "//drivers/peripheral/display/interfaces/include", - "//third_party/bounds_checking_function/include", - ] - } +config("display_hdi_public_config") { + visibility = [ ":*" ] + include_dirs = [ + "//drivers/peripheral/base", + "//drivers/peripheral/display/interfaces/include", + "//third_party/bounds_checking_function/include", + ] +} - group("hdi_display") { - deps = [ - ":hdi_display_device", - ":hdi_display_gfx", - ":hdi_display_gralloc", - ":hdi_display_layer", - "//drivers/peripheral/display/hdi_service/device:display_device_service", - "//drivers/peripheral/display/hdi_service/gralloc/client:hdi_gralloc_client", - "//drivers/peripheral/display/hdi_service/gralloc/server:hdi_gralloc_stub", - "//drivers/peripheral/display/hdi_service/video_layer/client:video_layer_client", - ] - if (target_cpu == "arm") { - deps += [ "//drivers/peripheral/display/hdi_service/video_layer/server:video_layer_service" ] - } - public_configs = [ ":display_hdi_public_config" ] +group("hdi_display") { + deps = [ + # ":hdi_display_device", + # ":hdi_display_gfx", + ":hdi_display_gralloc", + # ":hdi_display_layer", + # "//drivers/peripheral/display/hdi_service/device:display_device_service", + "//drivers/peripheral/display/hdi_service/gralloc/client:hdi_gralloc_client", + # "//drivers/peripheral/display/hdi_service/gralloc/server:hdi_gralloc_stub", + # "//drivers/peripheral/display/hdi_service/video_layer/client:video_layer_client", + ] + if (target_cpu == "arm") { + deps += [ "//drivers/peripheral/display/hdi_service/video_layer/server:video_layer_service" ] } + public_configs = [ ":display_hdi_public_config" ] +} - if (product_name != "ohos-arm64" && target_cpu != "x86_64") { - device_hal = display_device_hal - group("hdi_display_device") { - deps = [ "//device/${device_hal}/display:display_device" ] - public_configs = [ ":display_hdi_public_config" ] - } +# if (product_name != "ohos-arm64" && target_cpu != "x86_64") { +# device_hal = display_device_hal +# group("hdi_display_device") { +# deps = [ "//device/${device_hal}/display:display_device" ] +# public_configs = [ ":display_hdi_public_config" ] +# } - group("hdi_display_gralloc") { - deps = [ "//device/${device_hal}/display:display_gralloc" ] - public_configs = [ ":display_hdi_public_config" ] - } +# group("hdi_display_gralloc") { +# deps = [ "//device/${device_hal}/display:display_gralloc" ] +# public_configs = [ ":display_hdi_public_config" ] +# } - group("hdi_display_gfx") { - deps = [ "//device/${device_hal}/display:display_gfx" ] - public_configs = [ ":display_hdi_public_config" ] - } +# group("hdi_display_gfx") { +# deps = [ "//device/${device_hal}/display:display_gfx" ] +# public_configs = [ ":display_hdi_public_config" ] +# } - group("hdi_display_layer") { - deps = [ "//device/${device_hal}/display:display_layer" ] - public_configs = [ ":display_hdi_public_config" ] - } - } else { - group("hdi_display_device") { - deps = [ "//device/soc/hisilicon/common/hal/display:display_device" ] - public_configs = [ ":display_hdi_public_config" ] - } +# group("hdi_display_layer") { +# deps = [ "//device/${device_hal}/display:display_layer" ] +# public_configs = [ ":display_hdi_public_config" ] +# } +# } else { +# group("hdi_display_device") { +# deps = [ "//device/soc/hisilicon/common/hal/display:display_device" ] +# public_configs = [ ":display_hdi_public_config" ] +# } - group("hdi_display_gralloc") { - deps = [ "//device/soc/hisilicon/common/hal/display:display_gralloc" ] - public_configs = [ ":display_hdi_public_config" ] - } + group("hdi_display_gralloc") { + # deps = [ "//device/soc/hisilicon/common/hal/display:display_gralloc" ] + deps = [ "//drivers/peripheral/display/hal/openeuler/core/drm_backend/display_gralloc:display_gralloc" ] + public_configs = [ ":display_hdi_public_config" ] + } - group("hdi_display_gfx") { - deps = [ "//device/soc/hisilicon/common/hal/display:display_gfx" ] - public_configs = [ ":display_hdi_public_config" ] - } +# group("hdi_display_gfx") { +# deps = [ "//device/soc/hisilicon/common/hal/display:display_gfx" ] +# public_configs = [ ":display_hdi_public_config" ] +# } - group("hdi_display_layer") { - deps = [ "//device/soc/hisilicon/common/hal/display:display_layer" ] - public_configs = [ ":display_hdi_public_config" ] - } - } -} +# group("hdi_display_layer") { +# deps = [ "//device/soc/hisilicon/common/hal/display:display_layer" ] +# public_configs = [ ":display_hdi_public_config" ] +# } +# } diff --git a/display/hal/openeuler/base/log.h b/display/hal/openeuler/base/log.h index f58ccbe37792551e2032b62cf78a2d694cf4c125..c5bd2c71522ef896dc2eb047252db249c4e684b7 100644 --- a/display/hal/openeuler/base/log.h +++ b/display/hal/openeuler/base/log.h @@ -21,10 +21,11 @@ using namespace OHOS::HiviewDFX; -constexpr HiLogLabel LABEL = { LOG_CORE, 0xD001400, "SyncFence" }; +constexpr HiLogLabel LABEL = { LOG_CORE, 0xD001400, "DrmBackend" }; #define LOG_FATAL(format, ...) HiLog::Fatal(LABEL, format, ##__VA_ARGS__) #define LOG_ERROR(format, ...) HiLog::Error(LABEL, format, ##__VA_ARGS__) #define LOG_WARN(format, ...) HiLog::Warn(LABEL, format, ##__VA_ARGS__) #define LOG_INFO(format, ...) HiLog::Info(LABEL, format, ##__VA_ARGS__) #define LOG_DEBUG(format, ...) HiLog::Debug(LABEL, format, ##__VA_ARGS__) +#define LOG_TRACE(format, ...) HiLog::Trace(LABEL, format, ##__VA_ARGS__) diff --git a/display/hal/openeuler/core/drm_backend/display_device/BUILD.gn b/display/hal/openeuler/core/drm_backend/display_device/BUILD.gn index 504f82670c83aaa30c62c67cbbace48319f70713..7c620174deee29a2169b74f8041465fdae12c300 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/BUILD.gn +++ b/display/hal/openeuler/core/drm_backend/display_device/BUILD.gn @@ -16,6 +16,9 @@ config("display_device_public_config") { include_dirs = [ "//drivers/peripheral/display/hal/openeuler/core/drm_backend/display_device", "//drivers/peripheral/display/hal/openeuler/core/drm_backend/include", + + "//drivers/peripheral/display/interfaces/include", + "//drivers/peripheral/base", ] } @@ -38,22 +41,21 @@ ft_shared_library("display_device") { "hdi_session.cpp", ] - include_dirs = [ - "//drivers/peripheral/display/interfaces/include", - "//drivers/peripheral/base", - ] - configs = [ "//drivers/peripheral/display/hal/openeuler:oehal_public_config", - "//drivers/peripheral/display/hal/openeuler/core/drm_backend:import_system_gbm_config", ] - public_configs = [ ":display_device_public_config" ] + public_configs = [ + ":display_device_public_config", + "//drivers/peripheral/display/hal/openeuler/core/drm_backend:import_system_gbm_config" + ] - public_deps = [ "//drivers/peripheral/display/hal/openeuler/core/drm_backend:display_drm_dep" ] + public_deps = [ + "//drivers/peripheral/display/hal/openeuler/core/drm_backend:display_drm_dep", + "//drivers/peripheral/display/hal/openeuler/base:oebase", + ] deps = [ "//foundation/graphic/graphic_2d/utils/sync_fence:sync_fence", "//drivers/peripheral/display/hal/openeuler/core/event_loop:event_loop", - "//drivers/peripheral/display/hal/openeuler/base:oebase", ] part_name = "oehal" diff --git a/display/hal/openeuler/core/drm_backend/display_device/device_event_monitor.cpp b/display/hal/openeuler/core/drm_backend/display_device/device_event_monitor.cpp index fb45d105f513d1ebce2b140f3543cc14140f5cc0..589a9b2900f901b71a316445f10127d20f9e3fb6 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/device_event_monitor.cpp +++ b/display/hal/openeuler/core/drm_backend/display_device/device_event_monitor.cpp @@ -162,30 +162,39 @@ void DeviceEventMonitor::OnDrmVsyncEvent(TimeStamp timeStamp) void DeviceEventMonitor::OnSoftVsyncEvent(TimeStamp timeStamp) { - LOG_DEBUG("DeviceEventMonitor::OnSoftVsyncEvent, timestamp: %{public}s", timeStamp.ToFormattedString().c_str()); + static int i = 0; + if (i < 3) { + LOG_DEBUG("DeviceEventMonitor::OnSoftVsyncEvent, timestamp: %{public}s", timeStamp.ToFormattedString().c_str()); + ++i; + } + auto frame = detail::GetFrameCnt(); auto displays = drmDevice_->GetDisplays(); for (auto &[displayId, display] : displays) { - LOG_DEBUG("display %{public}u vsync...", displayId); + // LOG_DEBUG("display %{public}u vsync...", displayId); display->OnVSync(frame, static_cast(timeStamp.Get())); } } void DeviceEventMonitor::RegisterVsyncEventHandler() { - if (drmDevice_->SupportAtomicModeSet()) { - // TODO: drmEventContext VERSION - drmEventContext_->version = 3; - drmEventContext_->page_flip_handler = DrmPageFlipHandler; - vsyncChannel_ = std::make_unique(dupDrmFd_, loop_); - vsyncChannel_->SetReadCallback([this](TimeStamp timestamp) { OnDrmVsyncEvent(timestamp); }); - vsyncChannel_->EnableReading(true); - LOG_INFO("DeviceEventMonitor::RegisterVsyncEventHandler done by atomic mode setting."); - } else { - // use soft vsync. - softVsyncTimer_ = loop_->RunEvery([this]() { OnSoftVsyncEvent(TimeStamp::Now()); }, softVsyncPeriod_); - LOG_INFO("DeviceEventMonitor::RegisterVsyncEventHandler done by soft timer."); - } + // if (drmDevice_->SupportAtomicModeSet()) { + // // TODO: drmEventContext VERSION + // drmEventContext_->version = 3; + // drmEventContext_->page_flip_handler = DrmPageFlipHandler; + // vsyncChannel_ = std::make_unique(dupDrmFd_, loop_); + // vsyncChannel_->SetReadCallback([this](TimeStamp timestamp) { OnDrmVsyncEvent(timestamp); }); + // vsyncChannel_->EnableReading(true); + // LOG_INFO("DeviceEventMonitor::RegisterVsyncEventHandler done by atomic mode setting."); + // } else { + // // use soft vsync. + // softVsyncTimer_ = loop_->RunEvery([this]() { OnSoftVsyncEvent(TimeStamp::Now()); }, softVsyncPeriod_); + // LOG_INFO("DeviceEventMonitor::RegisterVsyncEventHandler done by soft timer."); + // } + + // use soft vsync. + softVsyncTimer_ = loop_->RunEvery([this]() { OnSoftVsyncEvent(TimeStamp::Now()); }, softVsyncPeriod_); + LOG_INFO("DeviceEventMonitor::RegisterVsyncEventHandler done by soft timer."); } bool DeviceEventMonitor::Init() diff --git a/display/hal/openeuler/core/drm_backend/display_device/drm_atomic_committer.cpp b/display/hal/openeuler/core/drm_backend/display_device/drm_atomic_committer.cpp index 9d56570a8dba16efc943f2c37f15d90ba2f4913e..880e49dbd145f03e50336429e2fd0376e45c5a76 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/drm_atomic_committer.cpp +++ b/display/hal/openeuler/core/drm_backend/display_device/drm_atomic_committer.cpp @@ -44,10 +44,10 @@ void DrmAtomicCommitter::AddAtomicProperty(uint32_t objId, uint32_t propId, uint void DrmAtomicCommitter::Commit() { - LOG_DEBUG("drmModeAtomicCommit drmFd: %{public}i, flags: %{public}i, userData: %{public}p", drmFd_, flags_, userData_); + LOG_DEBUG("DrmAtomicCommitter::Commit: drmFd: %{public}i, flags: %{public}i, userData: %{public}p", drmFd_, flags_, userData_); int ret = drmModeAtomicCommit(drmFd_, req_, flags_, userData_); if (ret < 0) { - LOG_ERROR("drmModeAtomicCommit failed, err: %{public}i, %{public}s", errno, ErrnoToString(errno).c_str()); + LOG_ERROR("DrmAtomicCommitter::Commit: failed, err: %{public}i, %{public}s", errno, ErrnoToString(errno).c_str()); } } } // namespace drm diff --git a/display/hal/openeuler/core/drm_backend/display_device/drm_connector.h b/display/hal/openeuler/core/drm_backend/display_device/drm_connector.h index 407f58a278d9767e1376df2287010ed5aee05b8d..7dcb8ac5935de4cb66228178157771314d9e691a 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/drm_connector.h +++ b/display/hal/openeuler/core/drm_backend/display_device/drm_connector.h @@ -165,8 +165,7 @@ private: uint32_t brightnessPropId_ = DRM_INVALID_PROP_ID; uint64_t brightness_ = DRM_INVLIAD_VALUE; std::vector> modes_; - // mode 3 is 1920*1080 - uint32_t activeModeId_ = 3; + uint32_t activeModeId_ = 0; // mode 3 is 1920*1080 }; } // namespace drm } // namespace oewm diff --git a/display/hal/openeuler/core/drm_backend/display_device/drm_device.cpp b/display/hal/openeuler/core/drm_backend/display_device/drm_device.cpp index 934dbd13dd1fb7ad833e0f1d36cb2ff8412db63b..aa41ab94064f9ed8614659fc5a7e84e775612358 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/drm_device.cpp +++ b/display/hal/openeuler/core/drm_backend/display_device/drm_device.cpp @@ -168,6 +168,8 @@ bool DrmDevice::InitKmsCaps() bool DrmDevice::Init() { + LOG_DEBUG("DrmDevice::Init"); + if (IsInvalidFd(fd_)) { LOG_ERROR("DrmDevice::Init: failed to open drm_device: %{public}s", devicePath_.c_str()); return false; @@ -201,6 +203,8 @@ bool DrmDevice::Init() SetupAllPlanes(); DiscoveryDisplays(); + LOG_DEBUG("DrmDevice::Init: done."); + return true; } @@ -252,6 +256,7 @@ void DrmDevice::DiscoveryDisplays() auto display = BuildHdiDisplayFromConnector(static_cast(i), connector); if (display != nullptr) { std::lock_guard lock(mutex_); + LOG_INFO("DrmDevice::DiscoveryDisplays: added display(%{public}u).", display->Id()); displays_[display->Id()] = std::move(display); } } diff --git a/display/hal/openeuler/core/drm_backend/display_device/drm_display.cpp b/display/hal/openeuler/core/drm_backend/display_device/drm_display.cpp index d31e3169d1eaa157c3ffa1f4bb65bcadcd2bc6cb..bc3fa285394f0de337f6d0e02481213a278f3082 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/drm_display.cpp +++ b/display/hal/openeuler/core/drm_backend/display_device/drm_display.cpp @@ -355,7 +355,7 @@ int32_t DrmDisplay::GetSupportedMetadataKey(uint32_t *num, HDRMetadataKey *keys) int32_t DrmDisplay::SetDisplayClientBuffer(const BufferHandle *buffer, int32_t fence) { - LOG_DEBUG("DrmDisplay::SetDisplayClientBuffer handle fd: %{public}i", buffer->fd); + // LOG_DEBUG("DrmDisplay::SetDisplayClientBuffer handle fd: %{public}i", buffer->fd); clientLayer_->SetBuffer(buffer, fence); return DISPLAY_SUCCESS; } @@ -422,18 +422,27 @@ void DrmDisplay::CommitAtomic(int32_t *fence, const DrmFrameBuffer *fb, int comm auto height = fb->GetFbHeight(); auto fbId = fb->GetFbId(); - // LOG_DEBUG << "DrmDisplay::CommitAtomic connectorId: " << connector_->Id() << ", crtcId: " << crtc_->Id() - // << ", blobId: " << connector_->BlobId() << ", connector_->CrtcPropId(): " << connector_->CrtcPropId() - // << ", crtc_->ModeIdPropId(): " << crtc_->ModeIdPropId() - // << ", crtc_->ActivePropId(): " << crtc_->ActivePropId() - // << ", crtc_->OutFencePropId(): " << crtc_->OutFencePropId() << ", fbId: " << fbId - // << ", fbWidth: " << width << ", fbHeight: " << height << "."; + LOG_DEBUG("DrmDisplay::CommitAtomic. connector Id: %{public}u, crtc Id: %{public}d, connector blobId: %{public}lu, " + "connector Crtc Prop ID: %{public}u, Crtc ModeId Prop ID: %{public}u, Crtc Active Prop ID: %{public}u, " + "Crtc OutFence Prop ID: %{public}u, FB Id: %{public}u, FB Width: %{public}u, FB Height: %{public}u.", + connector_->Id(), crtc_->Id(), connector_->BlobId(), connector_->CrtcPropId(), crtc_->ModeIdPropId(), + crtc_->ActivePropId(), crtc_->OutFencePropId(), fbId, width, height); DrmAtomicCommitter atomicAutoCommitter(drmFd_, commitFlag, this); + + /* set id of the CRTC id that the connector is using */ + atomicAutoCommitter.AddAtomicProperty(connector_->Id(), connector_->CrtcPropId(), crtc_->Id()); + + /* set the mode id of the CRTC; this property receives the id of a blob + * property that holds the struct that actually contains the mode info */ atomicAutoCommitter.AddAtomicProperty(crtc_->Id(), crtc_->ModeIdPropId(), connector_->BlobId()); + + /* set the CRTC object as active */ atomicAutoCommitter.AddAtomicProperty(crtc_->Id(), crtc_->ActivePropId(), 1); - atomicAutoCommitter.AddAtomicProperty(connector_->Id(), connector_->CrtcPropId(), crtc_->Id()); + atomicAutoCommitter.AddAtomicProperty(crtc_->Id(), crtc_->OutFencePropId(), (uint64_t)fence); + + /* set properties of the plane related to the CRTC and the framebuffer */ atomicAutoCommitter.AddAtomicProperty(primaryPlane_->Id(), primaryPlane_->FBPropId(), fbId); atomicAutoCommitter.AddAtomicProperty(primaryPlane_->Id(), primaryPlane_->CrtcPropId(), crtc_->Id()); atomicAutoCommitter.AddAtomicProperty(primaryPlane_->Id(), primaryPlane_->SrcXPropId(), 0); @@ -447,7 +456,7 @@ void DrmDisplay::CommitAtomic(int32_t *fence, const DrmFrameBuffer *fb, int comm atomicAutoCommitter.Commit(); clientLayer_->SetReleaseFence(*fence); - LOG_DEBUG("DrmDisplay::CommitAtomic done"); + LOG_DEBUG("DrmDisplay::CommitAtomic: done."); } void DrmDisplay::CommitLegacy(int32_t *fence, const DrmFrameBuffer *fb) diff --git a/display/hal/openeuler/core/drm_backend/display_device/hdi_display.cpp b/display/hal/openeuler/core/drm_backend/display_device/hdi_display.cpp index e2485a8e4578ad9781ff85d062904eae51a91e33..15400c43b1a2b736134753e52e252958ca251b0a 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/hdi_display.cpp +++ b/display/hal/openeuler/core/drm_backend/display_device/hdi_display.cpp @@ -24,6 +24,8 @@ HdiDisplay::HdiDisplay() {} bool HdiDisplay::Init() { + LOG_DEBUG("HdiDisplay::Init"); + auto id = GenerateLayerId(); auto layer = CreateHdiLayer(id, LAYER_TYPE_GRAPHIC); if (layer.get() == nullptr) { diff --git a/display/hal/openeuler/core/drm_backend/display_device/hdi_session.cpp b/display/hal/openeuler/core/drm_backend/display_device/hdi_session.cpp index 7216e503edb9964f997826b4f204ad7ff9c1b4e8..613ecb6f6c6820ec94f44b42dfd7a9cec063c4fe 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/hdi_session.cpp +++ b/display/hal/openeuler/core/drm_backend/display_device/hdi_session.cpp @@ -564,6 +564,7 @@ int32_t DeviceUnInitialize(DeviceFuncs *funcs) int32_t LayerInitialize(LayerFuncs **funcs) { + LOG_INFO("hdi layer initialize begin."); if (funcs == nullptr) { LOG_ERROR("LayerInitialize: param funcs is nullptr."); return DISPLAY_PARAM_ERR; @@ -591,7 +592,7 @@ int32_t LayerInitialize(LayerFuncs **funcs) layerFuncs->SetLayerTunnelHandle = SetLayerTunnelHandle; *funcs = layerFuncs; - LOG_INFO("layer initialize succeed."); + LOG_INFO("hdi layer initialize succeed."); return DISPLAY_SUCCESS; } diff --git a/display/hal/openeuler/core/drm_backend/display_device/hdi_session.h b/display/hal/openeuler/core/drm_backend/display_device/hdi_session.h index 07b562723ae575c6e7321c992eb1275eb4aa43b2..fb4617bb8550aadf6a09d2bb6ad325e9d58b9e9b 100644 --- a/display/hal/openeuler/core/drm_backend/display_device/hdi_session.h +++ b/display/hal/openeuler/core/drm_backend/display_device/hdi_session.h @@ -31,8 +31,8 @@ namespace DISPLAY { // singleton class HdiSession : NonCopyable { public: - static HdiSession &GetInstance(); ~HdiSession() noexcept; + static HdiSession &GetInstance(); const std::shared_ptr &GetDisplayDevice() const { diff --git a/display/hal/openeuler/core/drm_backend/display_gralloc/BUILD.gn b/display/hal/openeuler/core/drm_backend/display_gralloc/BUILD.gn index c5503bb51d695b363343aae554c3e3bec3192c29..8b1df6f26560ce4026491504235780442b3c2919 100644 --- a/display/hal/openeuler/core/drm_backend/display_gralloc/BUILD.gn +++ b/display/hal/openeuler/core/drm_backend/display_gralloc/BUILD.gn @@ -16,6 +16,9 @@ config("display_gralloc_public_config") { include_dirs = [ "//drivers/peripheral/display/hal/openeuler/core/drm_backend/display_gralloc", "//drivers/peripheral/display/hal/openeuler/core/drm_backend/include", + + "//drivers/peripheral/display/interfaces/include", + "//drivers/peripheral/base", ] } @@ -32,21 +35,20 @@ ft_shared_library("display_gralloc") { "shm_allocator.cpp", ] - include_dirs = [ - "//drivers/peripheral/display/interfaces/include", - "//drivers/peripheral/base", - ] - configs = [ - "//drivers/peripheral/display/hal/openeuler:oehal_public_config", - "//drivers/peripheral/display/hal/openeuler/core/drm_backend:import_system_gbm_config", + "//drivers/peripheral/display/hal/openeuler:oehal_public_config" + ] + public_configs = [ + ":display_gralloc_public_config", + "//drivers/peripheral/display/hal/openeuler/core/drm_backend:import_system_gbm_config" ] - public_configs = [ ":display_gralloc_public_config" ] - public_deps = [ "//drivers/peripheral/display/hal/openeuler/core/drm_backend:display_drm_dep" ] + public_deps = [ + "//drivers/peripheral/display/hal/openeuler/core/drm_backend:display_drm_dep", + "//drivers/peripheral/display/hal/openeuler/base:oebase" + ] deps = [ "//drivers/peripheral/display/hal/openeuler/core/drm_backend/display_device:display_device", - "//drivers/peripheral/display/hal/openeuler/base:oebase" ] part_name = "oehal" diff --git a/display/hal/openeuler/core/drm_backend/display_gralloc/allocator_controller.cpp b/display/hal/openeuler/core/drm_backend/display_gralloc/allocator_controller.cpp index 7d14b96af94bbed24312a6f08df6d77a9c528f6f..e5d10b5a917452b3a9efde0e4dc0c7229ee5c18a 100644 --- a/display/hal/openeuler/core/drm_backend/display_gralloc/allocator_controller.cpp +++ b/display/hal/openeuler/core/drm_backend/display_gralloc/allocator_controller.cpp @@ -27,6 +27,8 @@ namespace DISPLAY { int32_t AllocatorController::Init() { + LOG_DEBUG("[Gralloc::AllocatorController::Init] Init."); + gbmAllocator_.reset(new GbmAllocator()); gbmAllocator_->Init(); dumbAllocator_.reset(new DumbAllocator()); @@ -40,6 +42,8 @@ int32_t AllocatorController::Init() int32_t AllocatorController::Uninit() { + LOG_DEBUG("[Gralloc::AllocatorController::Uninit] Uninit."); + gbmAllocator_.reset(); dumbAllocator_.reset(); shmAllocator_.reset(); @@ -55,12 +59,14 @@ std::shared_ptr AllocatorController::GetAllocator(uint64_t usage) // * GBM: Smart Buffer for GPU Rendering (other case) if (usage & HBM_USE_MEM_DMA) { if (usage & HBM_USE_CPU_WRITE) { + LOG_DEBUG("[Gralloc::AllocatorController::GetAllocator] Choose Dumb Allocator."); return dumbAllocator_; } else { #ifdef DRM_BACKEND_USE_GBM + LOG_DEBUG("[Gralloc::AllocatorController::GetAllocator] Choose GBM Allocator."); return gbmAllocator_; #else - LOG_ERROR("[Gralloc] AllocatorController: DRM Backend not support GBM."); + LOG_ERROR("[Gralloc::AllocatorController::GetAllocator] DRM Backend not support GBM."); return nullptr; #endif // DRM_BACKEND_USE_GBM } @@ -71,7 +77,7 @@ std::shared_ptr AllocatorController::GetAllocator(uint64_t usage) return shmAllocator_; } - LOG_ERROR("[Gralloc] AllocatorController: This usage is not supported: %{public}" PRIu64, usage); + LOG_ERROR("[Gralloc::AllocatorController::GetAllocator] This usage is not supported: %{public}" PRIu64, usage); return nullptr; } diff --git a/display/hal/openeuler/core/drm_backend/display_gralloc/display_gralloc.cpp b/display/hal/openeuler/core/drm_backend/display_gralloc/display_gralloc.cpp index fd3fb2a5694ef02c708dc950f1f31609633ce33f..1103ceef02bfe24cbe8ef523392bf2bc60d45c27 100644 --- a/display/hal/openeuler/core/drm_backend/display_gralloc/display_gralloc.cpp +++ b/display/hal/openeuler/core/drm_backend/display_gralloc/display_gralloc.cpp @@ -54,9 +54,9 @@ int32_t AllocMem(const AllocInfo *info, BufferHandle **handle) auto allocator = AllocatorController::GetInstance().GetAllocator(info->usage); if (allocator == nullptr) { return DISPLAY_NULL_PTR; - } else { - return allocator->AllocMem(*info, handle); } + + return allocator->AllocMem(*info, handle); } void FreeMem(BufferHandle *handle) @@ -70,6 +70,7 @@ void FreeMem(BufferHandle *handle) if (allocator == nullptr) { return; } + allocator->FreeMem(handle); } @@ -83,9 +84,9 @@ void *Mmap(BufferHandle *handle) auto allocator = AllocatorController::GetInstance().GetAllocator(handle->usage); if (allocator == nullptr) { return nullptr; - } else { - return allocator->Mmap(*handle); } + + return allocator->Mmap(*handle); } int32_t Unmap(BufferHandle *handle) @@ -98,9 +99,9 @@ int32_t Unmap(BufferHandle *handle) auto allocator = AllocatorController::GetInstance().GetAllocator(handle->usage); if (allocator == nullptr) { return DISPLAY_NULL_PTR; - } else { - return allocator->InvalidateCache(*handle); } + + return allocator->InvalidateCache(*handle); } int32_t FlushCache(BufferHandle *handle) @@ -113,9 +114,9 @@ int32_t FlushCache(BufferHandle *handle) auto allocator = AllocatorController::GetInstance().GetAllocator(handle->usage); if (allocator == nullptr) { return DISPLAY_NULL_PTR; - } else { - return allocator->FlushCache(*handle); } + + return allocator->FlushCache(*handle); } int32_t InvalidateCache(BufferHandle *handle) diff --git a/display/hal/openeuler/core/drm_backend/display_gralloc/gbm_allocator.cpp b/display/hal/openeuler/core/drm_backend/display_gralloc/gbm_allocator.cpp index 716a4617e49dfa8982a1df1db17c33ff4907e679..cbffff79d435415d8e56d6bcd6ef0c1dc51bad12 100644 --- a/display/hal/openeuler/core/drm_backend/display_gralloc/gbm_allocator.cpp +++ b/display/hal/openeuler/core/drm_backend/display_gralloc/gbm_allocator.cpp @@ -63,11 +63,15 @@ int32_t GbmAllocator::Init() LOG_INFO("[Gralloc::GbmAllocator::Init] Using DRM node: %{public}s", drmName); free(drmName); + LOG_DEBUG("[Gralloc::GbmAllocator::Init] Init done."); + return DISPLAY_SUCCESS; } int32_t GbmAllocator::AllocMem(const AllocInfo &info, BufferHandle **bufferPtr) { + LOG_DEBUG("[Gralloc::GbmAllocator::AllocMem] Alloc Mem."); + if (bufferPtr == nullptr) { LOG_ERROR("[Gralloc::GbmAllocator::AllocMem] Get nullptr param: `buffer`"); return DISPLAY_PARAM_ERR; @@ -99,11 +103,15 @@ int32_t GbmAllocator::AllocMem(const AllocInfo &info, BufferHandle **bufferPtr) } } + LOG_DEBUG("[Gralloc::GbmAllocator::AllocMem] Alloc Mem done."); + return DISPLAY_SUCCESS; } int32_t GbmAllocator::FreeMem(BufferHandle *buffer) { + LOG_DEBUG("[Gralloc::GbmAllocator::FreeMem] Free Mem."); + if (buffer == nullptr) { LOG_ERROR("[Gralloc::GbmAllocator::FreeMem] Get nullptr param: `buffer`"); return DISPLAY_NULL_PTR; @@ -138,11 +146,15 @@ int32_t GbmAllocator::FreeMem(BufferHandle *buffer) /* free buffer handle */ delete buffer; + LOG_DEBUG("[Gralloc::GbmAllocator::FreeMem] Free Mem done."); + return DISPLAY_SUCCESS; } void *GbmAllocator::Mmap(BufferHandle &buffer) { + LOG_DEBUG("[Gralloc::GbmAllocator::Mmap] Mmap."); + auto handle = static_cast(buffer.key); auto gbmBo = GetGbmBo(handle); if (gbmBo == nullptr) { @@ -185,11 +197,14 @@ void *GbmAllocator::Mmap(BufferHandle &buffer) &mapData); // map_data & return address is the same buffer.virAddr = virAddr; + LOG_DEBUG("[Gralloc::GbmAllocator::Mmap] Mmap done."); return virAddr; } int32_t GbmAllocator::Unmap(BufferHandle &buffer) { + LOG_DEBUG("[Gralloc::GbmAllocator::Unmap] Unmap."); + if (buffer.virAddr == nullptr) { LOG_ERROR("[Gralloc::GbmAllocator::Unmap] Get null buffer.virAddr!"); return DISPLAY_NULL_PTR; @@ -209,21 +224,32 @@ int32_t GbmAllocator::Unmap(BufferHandle &buffer) gbm_bo_unmap(gbmBo, buffer.virAddr); buffer.virAddr = nullptr; + + LOG_DEBUG("[Gralloc::GbmAllocator::Unmap] Unmap done."); return DISPLAY_SUCCESS; } int32_t GbmAllocator::FlushCache(BufferHandle &buffer) { + LOG_DEBUG("[Gralloc::GbmAllocator::FlushCache] Flush Cache."); + // TODO: FlushCache is not supported yet. UNUSED(buffer); + + LOG_DEBUG("[Gralloc::GbmAllocator::FlushCache] Flush Cache done."); return DISPLAY_SUCCESS; } int32_t GbmAllocator::InvalidateCache(BufferHandle &buffer) { + LOG_DEBUG("[Gralloc::GbmAllocator::InvalidateCache] Invalidate Cache."); + // TODO: InvalidateCache is not supported yet. UNUSED(buffer); + + LOG_DEBUG("[Gralloc::GbmAllocator::InvalidateCache] Invalidate Cache done."); + return DISPLAY_SUCCESS; } @@ -268,6 +294,7 @@ int32_t GbmAllocator::AllocMemWithUsage(const AllocInfo &info, BufferHandle **bu { /* CORE: gbm bo */ // TODO: create with modifiers + LOG_DEBUG("[Gralloc::GbmAllocator::AllocMemWithUsage] create GBM buffer object."); struct gbm_bo *gbmBo = gbm_bo_create( gbmContext_, info.width, @@ -275,18 +302,18 @@ int32_t GbmAllocator::AllocMemWithUsage(const AllocInfo &info, BufferHandle **bu GBM_FORMAT_XRGB8888, // Q: Why Must be GBM_FORMAT_XRGB8888? usage); if (gbmBo == nullptr) { - LOG_ERROR("[Gralloc::GbmAllocator::AllocMem] Failed to create gbm bo."); + LOG_ERROR("[Gralloc::GbmAllocator::AllocMemWithUsage] Failed to create GBM buffer object."); return DISPLAY_FAILURE; } /* Set buffer handle rst */ - LOG_DEBUG("[Gralloc::GbmAllocator::AllocMem] Set buffer handle rst"); + LOG_DEBUG("[Gralloc::GbmAllocator::AllocMemWithUsage] Set buffer handle rst"); PriBufferHandle *priBuffer = new PriBufferHandle(); auto boHandle = static_cast(gbm_bo_get_handle(gbmBo).u32); priBuffer->hdl.fd = gbm_bo_get_fd(gbmBo); // TODO: use planes' fd if (priBuffer->hdl.fd < 0) { - LOG_WARN("[Gralloc::GbmAllocator::AllocMem] Failed to get fd from gbm bo."); + LOG_WARN("[Gralloc::GbmAllocator::AllocMemWithUsage] Failed to get fd from gbm bo."); // return DISPLAY_FAILURE; } priBuffer->hdl.stride = gbm_bo_get_stride(gbmBo); diff --git a/display/hal/openeuler/core/event_loop/event_channel.cpp b/display/hal/openeuler/core/event_loop/event_channel.cpp index 315c1d2ac59d5ce35ad9a1c169f7208d52f64890..8d1f01ed87602d0c065afdcabdef0e5a21078233 100644 --- a/display/hal/openeuler/core/event_loop/event_channel.cpp +++ b/display/hal/openeuler/core/event_loop/event_channel.cpp @@ -132,7 +132,7 @@ void EventChannel::HandleEventInner(TimeStamp receivedTime) } if (receivedEvents_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) { - LOG_DEBUG("read event in channel %{public}i.", fd_); + // LOG_DEBUG("read event in channel %{public}i.", fd_); if (readCallback_ != nullptr) { readCallback_(receivedTime); } diff --git a/display/hal/openeuler/test/BUILD.gn b/display/hal/openeuler/test/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..4d3a851dc034f53846d95d30ff0ddf35a3eecac4 --- /dev/null +++ b/display/hal/openeuler/test/BUILD.gn @@ -0,0 +1,51 @@ +# Copyright (c) 2023 Huawei Device Co., Ltd. +# 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. + +import("//build/gn/fangtian.gni") + +ft_executable("modeset_atomic") { + sources = [ + "drm_howto/modeset_atomic.c", + ] + + configs = [ "//drivers/peripheral/display/hal/openeuler/core/drm_backend:import_system_drm_config" ] + + subsystem_name = "test" + part_name = "drm" +} + +ft_executable("drm_backend_st") { + sources = [ + "system_test/main.cpp" + ] + + deps = [ + "//drivers/peripheral/display/hal/openeuler/core/drm_backend:drm-backend", + "//drivers/peripheral/display/hal/openeuler/core/event_loop:event_loop", + "//foundation/graphic/graphic_2d/utils/sync_fence:sync_fence", + ] + + configs = [ + "//drivers/peripheral/display/hal/openeuler:oehal_public_config" + ] + + subsystem_name = "test" + part_name = "drm" +} + +group("oe_drm_backend_test") { + deps = [ + ":modeset_atomic", + ":drm_backend_st" + ] +} \ No newline at end of file diff --git a/display/hal/openeuler/test/drm_howto/modeset_atomic.c b/display/hal/openeuler/test/drm_howto/modeset_atomic.c new file mode 100644 index 0000000000000000000000000000000000000000..aa9424ed72259043e262859f0d62753995af8b3d --- /dev/null +++ b/display/hal/openeuler/test/drm_howto/modeset_atomic.c @@ -0,0 +1,1210 @@ +/* + * modeset-atomic - DRM Atomic-API Modesetting Example + * Written 2019 by Ezequiel Garcia + * + * Dedicated to the Public Domain. + */ + +/* + * DRM Double-Buffered VSync'ed Atomic Modesetting Howto + * This example extends modeset-vsync.c, introducing planes and the + * atomic API. + * + * Planes can be used to blend or overlay images on top of a CRTC + * framebuffer during the scanout process. Not all hardware provide + * planes and the number of planes available is also limited. If there's + * not enough planes available or the hardware does not provide them, + * users should fallback to composition via GPU or CPU to blend or + * overlay the planes. Notice that this render process will result + * in delay, what justifies the usage of planes by modern hardware + * that needs to be fast. + * + * There are three types of planes: primary, cursor and overlay. For + * compatibility with legacy userspace, the default behavior is to expose + * only overlay planes to userspace (we're going to see in the code that + * we have to ask to receive all types of planes). A good example of plane + * usage is this: imagine a static desktop screen and the user is moving + * the cursor around. Only the cursor is moving. Instead of calculating the + * complete scene for each time the user moves its cursor, we can update only + * the cursor plane and it will be automatically overlayed by the hardware on + * top of the primary plane. There's no need of software composition in this + * case. + * + * But there was synchronisation problems related to multiple planes + * usage. The KMS API was not atomic, so you'd have to update the primary + * plane and then the overlay planes with distinct IOCTL's. This could lead + * to tearing and also some trouble related to blocking, so the atomic + * API was proposed to fix these problems. + * + * With the introduction of the KMS atomic API, all the planes could get + * updated in a single IOCTL, using drmModeAtomicCommit(). This can be + * either asynchronous or fully blocking. + * + * This example assumes that you are familiar with modeset-vsync. Only + * the differences between both files are highlighted here. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * A new struct is introduced: drm_object. It stores properties of certain + * objects (connectors, CRTC and planes) that are used in atomic modeset setup + * and also in atomic page-flips (all planes updated in a single IOCTL). + */ + +struct drm_object { + drmModeObjectProperties *props; + drmModePropertyRes **props_info; + uint32_t id; +}; + +struct modeset_buf { + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t size; + uint32_t handle; + uint8_t *map; + uint32_t fb; +}; + +struct modeset_output { + struct modeset_output *next; + + unsigned int front_buf; + struct modeset_buf bufs[2]; + + struct drm_object connector; + struct drm_object crtc; + struct drm_object plane; + + drmModeModeInfo mode; + uint32_t mode_blob_id; + uint32_t crtc_index; + + bool pflip_pending; + bool cleanup; + + uint8_t r, g, b; + bool r_up, g_up, b_up; +}; +static struct modeset_output *output_list = NULL; + +/* + * modeset_open() changes just a little bit. We now have to set that we're going + * to use the KMS atomic API and check if the device is capable of handling it. + */ + +static int modeset_open(int *out, const char *node) +{ + int fd, ret; + uint64_t cap; + + fd = open(node, O_RDWR | O_CLOEXEC); + if (fd < 0) { + ret = -errno; + fprintf(stderr, "cannot open '%s': %m\n", node); + return ret; + } + + /* Set that we want to receive all the types of planes in the list. This + * have to be done since, for legacy reasons, the default behavior is to + * expose only the overlay planes to the users. The atomic API only + * works if this is set. + */ + ret = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (ret) { + fprintf(stderr, "failed to set universal planes cap, %d\n", ret); + return ret; + } + + /* Here we set that we're going to use the KMS atomic API. It's supposed + * to set the DRM_CLIENT_CAP_UNIVERSAL_PLANES automatically, but it's a + * safe behavior to set it explicitly as we did in the previous + * commands. This is also good for learning purposes. + */ + ret = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); + if (ret) { + fprintf(stderr, "failed to set atomic cap, %d", ret); + return ret; + } + + if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap) < 0 || !cap) { + fprintf(stderr, "drm device '%s' does not support dumb buffers\n", + node); + close(fd); + return -EOPNOTSUPP; + } + + if (drmGetCap(fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) < 0 || !cap) { + fprintf(stderr, "drm device '%s' does not support atomic KMS\n", + node); + close(fd); + return -EOPNOTSUPP; + } + + *out = fd; + return 0; +} + +/* + * get_property_value() is a new function. Given a device, the properties of + * an object and a name, search for the value of property 'name'. If we can't + * find it, return -1. + */ + +static int64_t get_property_value(int fd, drmModeObjectPropertiesPtr props, + const char *name) +{ + drmModePropertyPtr prop; + uint64_t value; + bool found; + + found = false; + for (uint32_t j = 0; j < props->count_props && !found; j++) { + prop = drmModeGetProperty(fd, props->props[j]); + if (!strcmp(prop->name, name)) { + value = props->prop_values[j]; + found = true; + } + drmModeFreeProperty(prop); + } + + if (!found) + return -1; + return value; +} + +/* + * get_drm_object_properties() is a new helpfer function that retrieves + * the properties of a certain CRTC, plane or connector object. + */ + +static void modeset_get_object_properties(int fd, struct drm_object *obj, + uint32_t type) +{ + const char *type_str; + unsigned int i; + + obj->props = drmModeObjectGetProperties(fd, obj->id, type); + if (!obj->props) { + switch(type) { + case DRM_MODE_OBJECT_CONNECTOR: + type_str = "connector"; + break; + case DRM_MODE_OBJECT_PLANE: + type_str = "plane"; + break; + case DRM_MODE_OBJECT_CRTC: + type_str = "CRTC"; + break; + default: + type_str = "unknown type"; + break; + } + fprintf(stderr, "cannot get %s %d properties: %s\n", + type_str, obj->id, strerror(errno)); + return; + } + + obj->props_info = calloc(obj->props->count_props, sizeof(obj->props_info)); + for (i = 0; i < obj->props->count_props; i++) + obj->props_info[i] = drmModeGetProperty(fd, obj->props->props[i]); +} + +/* + * set_drm_object_property() is a new function. It sets a property value to a + * CRTC, plane or connector object. + */ + +static int set_drm_object_property(drmModeAtomicReq *req, struct drm_object *obj, + const char *name, uint64_t value) +{ + uint32_t prop_id = 0; + + for (uint32_t i = 0; i < obj->props->count_props; i++) { + if (!strcmp(obj->props_info[i]->name, name)) { + prop_id = obj->props_info[i]->prop_id; + break; + } + } + + if (prop_id == 0) { + fprintf(stderr, "no object property: %s\n", name); + return -EINVAL; + } + + return drmModeAtomicAddProperty(req, obj->id, prop_id, value); +} + +/* + * modeset_find_crtc() changes a little bit. Now we also have to save the CRTC + * index, and not only its id. + */ + +static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, + struct modeset_output *out) +{ + drmModeEncoder *enc; + int i, j; + uint32_t crtc; + struct modeset_output *iter; + + /* first try the currently conected encoder+crtc */ + if (conn->encoder_id) + enc = drmModeGetEncoder(fd, conn->encoder_id); + else + enc = NULL; + + if (enc) { + if (enc->crtc_id) { + crtc = enc->crtc_id; + for (iter = output_list; iter; iter = iter->next) { + if (iter->crtc.id == crtc) { + crtc = 0; + break; + } + } + + if (crtc > 0) { + drmModeFreeEncoder(enc); + out->crtc.id = crtc; + /* find the CRTC's index */ + for (int i = 0; i < res->count_crtcs; ++i) { + if (res->crtcs[i] == crtc) { + out->crtc_index = i; + break; + } + } + return 0; + } + } + + drmModeFreeEncoder(enc); + } + + /* If the connector is not currently bound to an encoder or if the + * encoder+crtc is already used by another connector (actually unlikely + * but lets be safe), iterate all other available encoders to find a + * matching CRTC. + */ + for (i = 0; i < conn->count_encoders; ++i) { + enc = drmModeGetEncoder(fd, conn->encoders[i]); + if (!enc) { + fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", + i, conn->encoders[i], errno); + continue; + } + + /* iterate all global CRTCs */ + for (j = 0; j < res->count_crtcs; ++j) { + /* check whether this CRTC works with the encoder */ + if (!(enc->possible_crtcs & (1 << j))) + continue; + + /* check that no other output already uses this CRTC */ + crtc = res->crtcs[j]; + for (iter = output_list; iter; iter = iter->next) { + if (iter->crtc.id == crtc) { + crtc = 0; + break; + } + } + + /* We have found a CRTC, so save it and return. Note + * that we have to save its index as well. The CRTC + * index (not its ID) will be used when searching for a + * suitable plane. + */ + if (crtc > 0) { + fprintf(stdout, "crtc %u found for encoder %u, will need full modeset\n", + crtc, conn->encoders[i]);; + drmModeFreeEncoder(enc); + out->crtc.id = crtc; + out->crtc_index = j; + return 0; + } + } + + drmModeFreeEncoder(enc); + } + + fprintf(stderr, "cannot find suitable crtc for connector %u\n", + conn->connector_id); + return -ENOENT; +} + +/* + * modeset_find_plane() is a new function. Given a certain combination + * of connector+CRTC, it looks for a primary plane for it. + */ + +static int modeset_find_plane(int fd, struct modeset_output *out) +{ + drmModePlaneResPtr plane_res; + bool found_primary = false; + uint32_t i; + int ret = -EINVAL; + + plane_res = drmModeGetPlaneResources(fd); + if (!plane_res) { + fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", + strerror(errno)); + return -ENOENT; + } + + /* iterates through all planes of a certain device */ + for (i = 0; (i < plane_res->count_planes) && !found_primary; i++) { + int plane_id = plane_res->planes[i]; + + drmModePlanePtr plane = drmModeGetPlane(fd, plane_id); + if (!plane) { + fprintf(stderr, "drmModeGetPlane(%u) failed: %s\n", plane_id, + strerror(errno)); + continue; + } + + /* check if the plane can be used by our CRTC */ + if (plane->possible_crtcs & (1 << out->crtc_index)) { + drmModeObjectPropertiesPtr props = + drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE); + + /* Get the "type" property to check if this is a primary + * plane. Type property is special, as its enum value is + * defined in UAPI headers. For the properties that are + * not defined in the UAPI headers, we would have to + * give kernel the property name and it would return the + * corresponding enum value. We could also do this for + * the "type" property, but it would make this simple + * example more complex. The reason why defining enum + * values for kernel properties in UAPI headers is + * deprecated is that string names are easier to both + * (userspace and kernel) make unique and keep + * consistent between drivers and kernel versions. But + * in order to not break userspace, some properties were + * left in the UAPI headers as well. + */ + if (get_property_value(fd, props, "type") == DRM_PLANE_TYPE_PRIMARY) { + found_primary = true; + out->plane.id = plane_id; + ret = 0; + } + + drmModeFreeObjectProperties(props); + } + + drmModeFreePlane(plane); + } + + drmModeFreePlaneResources(plane_res); + + if (found_primary) + fprintf(stdout, "found primary plane, id: %d\n", out->plane.id); + else + fprintf(stdout, "couldn't find a primary plane\n"); + return ret; +} + +/* + * modeset_drm_object_fini() is a new helper function that destroys CRTCs, + * connectors and planes + */ + +static void modeset_drm_object_fini(struct drm_object *obj) +{ + for (uint32_t i = 0; i < obj->props->count_props; i++) + drmModeFreeProperty(obj->props_info[i]); + free(obj->props_info); + drmModeFreeObjectProperties(obj->props); +} + +/* + * modeset_setup_objects() is a new function. It helps us to retrieve + * connector, CRTC and plane objects properties from the device. These + * properties will help us during the atomic modesetting commit, so we save + * them in our struct modeset_output object. + */ + +static int modeset_setup_objects(int fd, struct modeset_output *out) +{ + struct drm_object *connector = &out->connector; + struct drm_object *crtc = &out->crtc; + struct drm_object *plane = &out->plane; + + /* retrieve connector properties from the device */ + modeset_get_object_properties(fd, connector, DRM_MODE_OBJECT_CONNECTOR); + if (!connector->props) + goto out_conn; + + /* retrieve CRTC properties from the device */ + modeset_get_object_properties(fd, crtc, DRM_MODE_OBJECT_CRTC); + if (!crtc->props) + goto out_crtc; + + /* retrieve plane properties from the device */ + modeset_get_object_properties(fd, plane, DRM_MODE_OBJECT_PLANE); + if (!plane->props) + goto out_plane; + + return 0; + +out_plane: + modeset_drm_object_fini(crtc); +out_crtc: + modeset_drm_object_fini(connector); +out_conn: + return -ENOMEM; +} + +/* + * modeset_destroy_objects() is a new function. It destroys what we allocate + * in modeset_setup_objects(). + */ + +static void modeset_destroy_objects(int fd, struct modeset_output *out) +{ + modeset_drm_object_fini(&out->connector); + modeset_drm_object_fini(&out->crtc); + modeset_drm_object_fini(&out->plane); +} + +/* + * modeset_create_fb() stays the same. + */ + +static int modeset_create_fb(int fd, struct modeset_buf *buf) +{ + struct drm_mode_create_dumb creq; + struct drm_mode_destroy_dumb dreq; + struct drm_mode_map_dumb mreq; + int ret; + uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; + + /* create dumb buffer */ + memset(&creq, 0, sizeof(creq)); + creq.width = buf->width; + creq.height = buf->height; + creq.bpp = 32; + ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); + if (ret < 0) { + fprintf(stderr, "cannot create dumb buffer (%d): %m\n", + errno); + return -errno; + } + buf->stride = creq.pitch; + buf->size = creq.size; + buf->handle = creq.handle; + + /* create framebuffer object for the dumb-buffer */ + handles[0] = buf->handle; + pitches[0] = buf->stride; + ret = drmModeAddFB2(fd, buf->width, buf->height, DRM_FORMAT_XRGB8888, + handles, pitches, offsets, &buf->fb, 0); + if (ret) { + fprintf(stderr, "cannot create framebuffer (%d): %m\n", + errno); + ret = -errno; + goto err_destroy; + } + + /* prepare buffer for memory mapping */ + memset(&mreq, 0, sizeof(mreq)); + mreq.handle = buf->handle; + ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); + if (ret) { + fprintf(stderr, "cannot map dumb buffer (%d): %m\n", + errno); + ret = -errno; + goto err_fb; + } + + /* perform actual memory mapping */ + buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, + fd, mreq.offset); + if (buf->map == MAP_FAILED) { + fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", + errno); + ret = -errno; + goto err_fb; + } + + /* clear the framebuffer to 0 */ + memset(buf->map, 0, buf->size); + + return 0; + +err_fb: + drmModeRmFB(fd, buf->fb); +err_destroy: + memset(&dreq, 0, sizeof(dreq)); + dreq.handle = buf->handle; + drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + return ret; +} + +/* + * modeset_destroy_fb() stays the same. + */ + +static void modeset_destroy_fb(int fd, struct modeset_buf *buf) +{ + struct drm_mode_destroy_dumb dreq; + + /* unmap buffer */ + munmap(buf->map, buf->size); + + /* delete framebuffer */ + drmModeRmFB(fd, buf->fb); + + /* delete dumb buffer */ + memset(&dreq, 0, sizeof(dreq)); + dreq.handle = buf->handle; + drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); +} + +/* + * modeset_setup_framebuffers() creates framebuffers for the back and front + * buffers of a certain output. Also, it copies the connector mode to these + * buffers. + */ + +static int modeset_setup_framebuffers(int fd, drmModeConnector *conn, + struct modeset_output *out) +{ + int i, ret; + + /* setup the front and back framebuffers */ + for (i = 0; i < 2; i++) { + + /* copy mode info to buffer */ + out->bufs[i].width = conn->modes[0].hdisplay; + out->bufs[i].height = conn->modes[0].vdisplay; + + /* create a framebuffer for the buffer */ + ret = modeset_create_fb(fd, &out->bufs[i]); + if (ret) { + /* the second framebuffer creation failed, so + * we have to destroy the first before returning */ + if (i == 1) + modeset_destroy_fb(fd, &out->bufs[0]); + return ret; + } + } + + return 0; +} + +/* + * modeset_output_destroy() is new. It destroys the objects (connector, crtc and + * plane), front and back buffers, the mode blob property and then destroys the + * output itself. + */ + +static void modeset_output_destroy(int fd, struct modeset_output *out) +{ + /* destroy connector, crtc and plane objects */ + modeset_destroy_objects(fd, out); + + /* destroy front/back framebuffers */ + modeset_destroy_fb(fd, &out->bufs[0]); + modeset_destroy_fb(fd, &out->bufs[1]); + + /* destroy mode blob property */ + drmModeDestroyPropertyBlob(fd, out->mode_blob_id); + + free(out); +} + +/* + * With a certain combination of connector+CRTC, we look for a suitable primary + * plane for it. After that, we retrieve connector, CRTC and plane objects + * properties from the device. These objects are used during the atomic modeset + * setup (see modeset_atomic_prepare_commit()) and also during the page-flips + * (see modeset_draw_out() and modeset_atomic_commit()). + * + * Besides that, we have to create a blob property that receives the output + * mode. When we perform an atomic commit, the driver expects a CRTC property + * named "MODE_ID", which points to the id of a blob. This usually happens for + * properties that are not simple types. In this particular case, out->mode is a + * struct. But we could have another property that expects the id of a blob that + * holds an array, for instance. + */ + +static struct modeset_output *modeset_output_create(int fd, drmModeRes *res, + drmModeConnector *conn) +{ + int ret; + struct modeset_output *out; + + /* creates an output structure */ + out = malloc(sizeof(*out)); + memset(out, 0, sizeof(*out)); + out->connector.id = conn->connector_id; + + /* check if a monitor is connected */ + if (conn->connection != DRM_MODE_CONNECTED) { + fprintf(stderr, "ignoring unused connector %u\n", + conn->connector_id); + goto out_error; + } + + /* check if there is at least one valid mode */ + if (conn->count_modes == 0) { + fprintf(stderr, "no valid mode for connector %u\n", + conn->connector_id); + goto out_error; + } + + /* copy the mode information into our output structure */ + memcpy(&out->mode, &conn->modes[0], sizeof(out->mode)); + /* create the blob property using out->mode and save its id in the output*/ + if (drmModeCreatePropertyBlob(fd, &out->mode, sizeof(out->mode), + &out->mode_blob_id) != 0) { + fprintf(stderr, "couldn't create a blob property\n"); + goto out_error; + } + fprintf(stderr, "mode for connector %u is %ux%u\n", + conn->connector_id, out->bufs[0].width, out->bufs[0].height); + + /* find a crtc for this connector */ + ret = modeset_find_crtc(fd, res, conn, out); + if (ret) { + fprintf(stderr, "no valid crtc for connector %u\n", + conn->connector_id); + goto out_blob; + } + + /* with a connector and crtc, find a primary plane */ + ret = modeset_find_plane(fd, out); + if (ret) { + fprintf(stderr, "no valid plane for crtc %u\n", out->crtc.id); + goto out_blob; + } + + /* gather properties of our connector, CRTC and planes */ + ret = modeset_setup_objects(fd, out); + if (ret) { + fprintf(stderr, "cannot get plane properties\n"); + goto out_blob; + } + + /* setup front/back framebuffers for this CRTC */ + ret = modeset_setup_framebuffers(fd, conn, out); + if (ret) { + fprintf(stderr, "cannot create framebuffers for connector %u\n", + conn->connector_id); + goto out_obj; + } + + return out; + +out_obj: + modeset_destroy_objects(fd, out); +out_blob: + drmModeDestroyPropertyBlob(fd, out->mode_blob_id); +out_error: + free(out); + return NULL; +} + +/* + * modeset_prepare() changes a little bit. Now we use the new function + * modeset_output_create() to allocate memory and setup the output. + */ + +static int modeset_prepare(int fd) +{ + drmModeRes *res; + drmModeConnector *conn; + int i; + struct modeset_output *out; + + /* retrieve resources */ + res = drmModeGetResources(fd); + if (!res) { + fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", + errno); + return -errno; + } + + /* iterate all connectors */ + for (i = 0; i < res->count_connectors; ++i) { + /* get information for each connector */ + conn = drmModeGetConnector(fd, res->connectors[i]); + if (!conn) { + fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", + i, res->connectors[i], errno); + continue; + } + + /* create an output structure and free connector data */ + out = modeset_output_create(fd, res, conn); + drmModeFreeConnector(conn); + if (!out) + continue; + + /* link output into global list */ + out->next = output_list; + output_list = out; + } + if (!output_list) { + fprintf(stderr, "couldn't create any outputs\n"); + return -1; + } + + /* free resources again */ + drmModeFreeResources(res); + return 0; +} + +/* + * modeset_atomic_prepare_commit() is new. Here we set the values of properties + * (of our connector, CRTC and plane objects) that we want to change in the + * atomic commit. These changes are temporarily stored in drmModeAtomicReq *req + * until the commit actually happens. + */ + +static int modeset_atomic_prepare_commit(int fd, struct modeset_output *out, + drmModeAtomicReq *req) +{ + struct drm_object *plane = &out->plane; + struct modeset_buf *buf = &out->bufs[out->front_buf ^ 1]; + + /* set id of the CRTC id that the connector is using */ + if (set_drm_object_property(req, &out->connector, "CRTC_ID", out->crtc.id) < 0) + return -1; + + /* set the mode id of the CRTC; this property receives the id of a blob + * property that holds the struct that actually contains the mode info */ + if (set_drm_object_property(req, &out->crtc, "MODE_ID", out->mode_blob_id) < 0) + return -1; + + /* set the CRTC object as active */ + if (set_drm_object_property(req, &out->crtc, "ACTIVE", 1) < 0) + return -1; + + /* set properties of the plane related to the CRTC and the framebuffer */ + if (set_drm_object_property(req, plane, "FB_ID", buf->fb) < 0) + return -1; + if (set_drm_object_property(req, plane, "CRTC_ID", out->crtc.id) < 0) + return -1; + if (set_drm_object_property(req, plane, "SRC_X", 0) < 0) + return -1; + if (set_drm_object_property(req, plane, "SRC_Y", 0) < 0) + return -1; + if (set_drm_object_property(req, plane, "SRC_W", buf->width << 16) < 0) + return -1; + if (set_drm_object_property(req, plane, "SRC_H", buf->height << 16) < 0) + return -1; + if (set_drm_object_property(req, plane, "CRTC_X", 0) < 0) + return -1; + if (set_drm_object_property(req, plane, "CRTC_Y", 0) < 0) + return -1; + if (set_drm_object_property(req, plane, "CRTC_W", buf->width) < 0) + return -1; + if (set_drm_object_property(req, plane, "CRTC_H", buf->height) < 0) + return -1; + + return 0; +} + +/* + * A short helper function to compute a changing color value. No need to + * understand it. + */ + +static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) +{ + uint8_t next; + + next = cur + (*up ? 1 : -1) * (rand() % mod); + if ((*up && next < cur) || (!*up && next > cur)) { + *up = !*up; + next = cur; + } + + return next; +} + +/* + * Draw on back framebuffer before the page-flip is requested. + */ + +static void modeset_paint_framebuffer(struct modeset_output *out) +{ + struct modeset_buf *buf; + unsigned int j, k, off; + + /* draw on back framebuffer */ + out->r = next_color(&out->r_up, out->r, 5); + out->g = next_color(&out->g_up, out->g, 5); + out->b = next_color(&out->b_up, out->b, 5); + buf = &out->bufs[out->front_buf ^ 1]; + for (j = 0; j < buf->height; ++j) { + for (k = 0; k < buf->width; ++k) { + off = buf->stride * j + k * 4; + *(uint32_t*)&buf->map[off] = + (out->r << 16) | (out->g << 8) | out->b; + } + } + +} + +/* + * modeset_draw_out() prepares the framebuffer with the drawing and then it asks + * for the driver to perform an atomic commit. This will lead to a page-flip and + * the content of the framebuffer will be displayed. In this simple example + * we're only using the primary plane, but we could also be updating other + * planes in the same atomic commit. + * + * Just like in modeset_perform_modeset(), we first setup everything with + * modeset_atomic_prepare_commit() and then actually perform the atomic commit. + * But there are some important differences: + * + * 1. Here we just want to perform a commit that changes the state of a specific + * output, and in modeset_perform_modeset() we did an atomic commit that was + * supposed to setup all the outputs at once. So there's no need to prepare + * every output before performing the atomic commit. But let's suppose you + * prepare every output and then perform the commit. It should schedule a + * page-flip for all of them, but modeset_draw_out() was called because the + * page-flip for a specific output has finished. The others may not be + * prepared for a page-flip yet (e.g. in the middle of a scanout), so these + * page-flips will fail. + * + * 2. Here we have already painted the framebuffer and also we don't use the + * flag DRM_MODE_ALLOW_MODESET anymore, since the modeset already happened. + * We could continue to use this flag, as it makes no difference if + * modeset_perform_modeset() is correct and there's no bug in the kernel. + * The flag only allows (it doesn't force) the driver to perform a modeset, + * but we have already performed it in modeset_perform_modeset() and now we + * just want page-flips to occur. If we still need to perform modesets it + * means that we have a bug somewhere, and it may be better to fail than to + * glitch (a modeset can cause unecessary latency and also blank the screen). + */ + +static void modeset_draw_out(int fd, struct modeset_output *out) +{ + drmModeAtomicReq *req; + int ret, flags; + + /* draw on framebuffer of the output */ + modeset_paint_framebuffer(out); + + /* prepare output for atomic commit */ + req = drmModeAtomicAlloc(); + ret = modeset_atomic_prepare_commit(fd, out, req); + if (ret < 0) { + fprintf(stderr, "prepare atomic commit failed, %d\n", errno); + return; + } + + /* We've just draw on the framebuffer, prepared the commit and now it's + * time to perform a page-flip to display its content. + * + * DRM_MODE_PAGE_FLIP_EVENT signalizes that we want to receive a + * page-flip event in the DRM-fd when the page-flip happens. This flag + * is also used in the non-atomic examples, so you're probably familiar + * with it. + * + * DRM_MODE_ATOMIC_NONBLOCK makes the page-flip non-blocking. We don't + * want to be blocked waiting for the commit to happen, since we can use + * this time to prepare a new framebuffer, for instance. We can only do + * this because there are mechanisms to know when the commit is complete + * (like page flip event, explained above). + */ + flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; + ret = drmModeAtomicCommit(fd, req, flags, NULL); + drmModeAtomicFree(req); + + if (ret < 0) { + fprintf(stderr, "atomic commit failed, %d\n", errno); + return; + } + out->front_buf ^= 1; + out->pflip_pending = true; +} + +/* + * modeset_page_flip_event() changes. Now that we are using page_flip_handler2, + * we also receive the CRTC that is responsible for this event. When using the + * atomic API we commit multiple CRTC's at once, so we need the information of + * what output caused the event in order to schedule a new page-flip for it. + */ + +static void modeset_page_flip_event(int fd, unsigned int frame, + unsigned int sec, unsigned int usec, + unsigned int crtc_id, void *data) +{ + struct modeset_output *out, *iter; + + /* find the output responsible for this event */ + out = NULL; + for (iter = output_list; iter; iter = iter->next) { + if (iter->crtc.id == crtc_id) { + out = iter; + break; + } + } + if (out == NULL) + return; + + out->pflip_pending = false; + if (!out->cleanup) + modeset_draw_out(fd, out); +} + +/* + * modeset_perform_modeset() is new. First we define what properties have to be + * changed and the values that they will receive. To check if the modeset will + * work as expected, we perform an atomic commit with the flag + * DRM_MODE_ATOMIC_TEST_ONLY. With this flag the DRM driver tests if the atomic + * commit would work, but it doesn't commit it to the hardware. After, the same + * atomic commit is performed without the TEST_ONLY flag, but not only before we + * draw on the framebuffers of the outputs. This is necessary to avoid + * displaying unwanted content. + * + * NOTE: we can't perform an atomic commit without an attached frambeuffer + * (even when we have DRM_MODE_ATOMIC_TEST_ONLY). It will simply fail. + */ + +static int modeset_perform_modeset(int fd) +{ + int ret, flags; + struct modeset_output *iter; + drmModeAtomicReq *req; + + /* prepare modeset on all outputs */ + req = drmModeAtomicAlloc(); + for (iter = output_list; iter; iter = iter->next) { + ret = modeset_atomic_prepare_commit(fd, iter, req); + if (ret < 0) + break; + } + if (ret < 0) { + fprintf(stderr, "prepare atomic commit failed, %d\n", errno); + return ret; + } + + /* perform test-only atomic commit */ + flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET; + ret = drmModeAtomicCommit(fd, req, flags, NULL); + if (ret < 0) { + fprintf(stderr, "test-only atomic commit failed : (%d)%s\n", errno, strerror(errno)); + drmModeAtomicFree(req); + return ret; + } + + + /* draw on back framebuffer of all outputs */ + for (iter = output_list; iter; iter = iter->next) { + + /* colors initialization, this is the first time we're drawing */ + iter->r = rand() % 0xff; + iter->g = rand() % 0xff; + iter->b = rand() % 0xff; + iter->r_up = iter->g_up = iter->b_up = true; + + modeset_paint_framebuffer(iter); + } + + /* initial modeset on all outputs */ + flags = DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT; + ret = drmModeAtomicCommit(fd, req, flags, NULL); + if (ret < 0) + fprintf(stderr, "modeset atomic commit failed, %d\n", errno); + + drmModeAtomicFree(req); + + return ret; +} + +/* + * modeset_draw() changes. If we got here, the modeset already occurred. When + * the page-flip for a certain output is done, an event will be fired and we'll + * be able to handle it. + * + * Here we define the function that should handle these events, which is + * modeset_page_flip_event(). This function calls modeset_draw_out(), which is + * responsible for preparing a new framebuffer and performing another atomic + * commit for us. + * + * Then we have a 5 seconds loop that keeps waiting for the events that are + * fired when the page-flip is complete. drmHandleEvent() is reponsible for + * reading the events from the fd and to call modeset_page_flip_event() for + * each one of them. + */ + +static void modeset_draw(int fd) +{ + int ret; + fd_set fds; + time_t start, cur; + struct timeval v; + drmEventContext ev; + + /* init variables */ + srand(time(&start)); + FD_ZERO(&fds); + memset(&v, 0, sizeof(v)); + memset(&ev, 0, sizeof(ev)); + + /* 3 is the first version that allow us to use page_flip_handler2, which + * is just like page_flip_handler but with the addition of passing the + * crtc_id as argument to the function that will handle page-flip events + * (in our case, modeset_page_flip_event()). This is good because we can + * find out for what output the page-flip happened. + * + * The usage of page_flip_handler2 is the reason why we needed to verify + * the support for DRM_CAP_CRTC_IN_VBLANK_EVENT. + */ + ev.version = 3; + ev.page_flip_handler2 = modeset_page_flip_event; + + /* perform modeset using atomic commit */ + modeset_perform_modeset(fd); + + /* wait 5s for VBLANK or input events */ + while (time(&cur) < start + 5) { + FD_SET(0, &fds); + FD_SET(fd, &fds); + v.tv_sec = start + 5 - cur; + + ret = select(fd + 1, &fds, NULL, NULL, &v); + if (ret < 0) { + fprintf(stderr, "select() failed with %d: %m\n", errno); + break; + } else if (FD_ISSET(0, &fds)) { + fprintf(stderr, "exit due to user-input\n"); + break; + } else if (FD_ISSET(fd, &fds)) { + /* read the fd looking for events and handle each event + * by calling modeset_page_flip_event() */ + drmHandleEvent(fd, &ev); + } + } +} + +/* + * modeset_cleanup() stays the same. + */ + +static void modeset_cleanup(int fd) +{ + struct modeset_output *iter; + drmEventContext ev; + int ret; + + /* init variables */ + memset(&ev, 0, sizeof(ev)); + ev.version = 3; + ev.page_flip_handler2 = modeset_page_flip_event; + + while (output_list) { + /* get first output from list */ + iter = output_list; + + /* if a page-flip is pending, wait for it to complete */ + iter->cleanup = true; + fprintf(stderr, "wait for pending page-flip to complete...\n"); + while (iter->pflip_pending) { + ret = drmHandleEvent(fd, &ev); + if (ret) + break; + } + + /* move head of the list to the next output */ + output_list = iter->next; + + /* destroy current output */ + modeset_output_destroy(fd, iter); + } +} + +/* + * main() also changes. Instead of performing the KMS setup calling + * drmModeSetCrtc(), we instead setup it using the atomic API with the + * function modeset_perform_modeset(), which is called by modeset_draw(). + */ + +int main(int argc, char **argv) +{ + int ret, fd; + const char *card; + + /* check which DRM device to open */ + if (argc > 1) + card = argv[1]; + else + card = "/dev/dri/card0"; + + fprintf(stderr, "using card '%s'\n", card); + + /* open the DRM device */ + ret = modeset_open(&fd, card); + if (ret) + goto out_return; + + /* prepare all connectors and CRTCs */ + ret = modeset_prepare(fd); + if (ret) + goto out_close; + + /* draw some colors for 5seconds */ + modeset_draw(fd); + + /* cleanup everything */ + modeset_cleanup(fd); + + ret = 0; + +out_close: + close(fd); +out_return: + if (ret) { + errno = -ret; + fprintf(stderr, "modeset failed with error %d: %m\n", errno); + } else { + fprintf(stderr, "exiting\n"); + } + return ret; +} + +/* + * This is a very simple example to show how to use the KMS atomic API and how + * different it is from legacy KMS. Most modern drivers are using the atomic + * API, so it is important to have this example. + * + * Just like vsync'ed double-buffering, the atomic API does not not solve all + * the problems that can happen and you have to figure out the best + * implementation for your use case. + * + * If you want to take a look at more complex examples that makes use of KMS + * atomic API, I can recommend you: + * + * - kms-quads: https://gitlab.freedesktop.org/daniels/kms-quads/ + * A more complex (but also well-explained) example that can be used to + * learn how to build a compositor using the atomic API. Supports both + * GL and software rendering. + * + * - Weston: https://gitlab.freedesktop.org/wayland/weston + * Reference implementation of a Wayland compositor. It's a very + * sophisticated DRM renderer, hard to understand fully as it uses more + * complicated techniques like DRM planes. + * + * Any feedback is welcome. Feel free to use this code freely for your own + * documentation or projects. + * + * - Hosted on http://github.com/dvdhrm/docs + */ \ No newline at end of file diff --git a/display/hal/openeuler/test/system_test/main.cpp b/display/hal/openeuler/test/system_test/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..77d5b7c86f0ab4018c84087c8f07067828bc278b --- /dev/null +++ b/display/hal/openeuler/test/system_test/main.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * 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 "display_device.h" +#include "event_loop/event_loop.h" +#include "hdi_display.h" +#include "hdi_session.h" +#include "sync_fence.h" +#include "allocator_controller.h" + +oewm::HDI::DISPLAY::HdiSession& g_session = oewm::HDI::DISPLAY::HdiSession::GetInstance(); +oewm::HDI::DISPLAY::AllocatorController& g_alloc_controller = oewm::HDI::DISPLAY::AllocatorController::GetInstance(); +oewm::EventLoop g_mainLoop = oewm::EventLoop(); + +bool DestoryBufferHandle(BufferHandle **handle); + +struct FrameBuffer { + BufferHandle *handle; +}; + +class Screen { +public: + ~Screen() noexcept { + if (fb[0]) { + DestoryBufferHandle(&fb[0]->handle); + } + if (fb[1]) { + DestoryBufferHandle(&fb[1]->handle); + } + } + static void OnVsync(uint32_t sequence, uint64_t timestamp, void *data); + +public: + uint32_t devId = ~0x0; + bool firstFrame = false; + uint32_t fbIdx = 0; + std::shared_ptr fb[2]; +}; +std::unordered_map g_screens; + +bool CreateFrameBuffer(uint32_t devId, BufferHandle **handle) +{ + printf("CreateFrameBuffer: start\n"); + + /* Get Display modes */ + std::vector displayModeInfos = {}; + uint32_t num = 0; + + int32_t ret = g_session.CallDisplayFunction( + devId, + &oewm::HDI::DISPLAY::HdiDisplay::GetDisplaySupportedModes, + &num, + (DisplayModeInfo*)nullptr + ); + if (ret != DISPLAY_SUCCESS) { + printf("CreateFrameBuffer: Failed to get display supported modes, ret=%d\n", ret); + return false; + } + if (num <= 0) { + printf("CreateFrameBuffer: display modes is %d, exited.\n", num); + return false; + } + printf("CreateFrameBuffer: display modes num=%d\n", num); + + displayModeInfos.resize(num); + ret = g_session.CallDisplayFunction( + devId, + &oewm::HDI::DISPLAY::HdiDisplay::GetDisplaySupportedModes, + &num, + displayModeInfos.data() + ); + if (ret != DISPLAY_SUCCESS) { + printf("CreateFrameBuffer: Failed to get screen supported modes, ret=%d\n", ret); + return false; + } + + /* Alloc frame buffer */ + // Choose mode + static const uint32_t DEFAULT_MODE_INDEX = 0; + uint32_t width = displayModeInfos[DEFAULT_MODE_INDEX].width; + uint32_t height = displayModeInfos[DEFAULT_MODE_INDEX].height; + ret = g_session.CallDisplayFunction( + devId, + &oewm::HDI::DISPLAY::HdiDisplay::SetDisplayMode, + DEFAULT_MODE_INDEX); + if (ret != DISPLAY_SUCCESS) { + printf("Draw: Failed to set display mode, ret=%d\n", ret); + return false; + } + printf("CreateFrameBuffer: choose display mode 0 to create fb: width=%d, height=%d.\n", width, height); + + // Get allocator + AllocInfo info = { + .width = width, + .height = height, + .usage = HBM_USE_MEM_DMA | HBM_USE_CPU_READ | HBM_USE_MEM_FB | HBM_USE_CPU_WRITE, // allocate dumb buffer with "HBM_USE_CPU_WRITE" + .format = PIXEL_FMT_BGRA_8888}; + auto allocator = g_alloc_controller.GetAllocator(info.usage); + if (allocator == nullptr) { + printf("CreateFrameBuffer: Failed to get buffer allocator.\n"); + return false; + } + + // Do allocate memory + ret = allocator->AllocMem(info, handle); + if (*handle == nullptr || ret != DISPLAY_SUCCESS) { + printf("CreateFrameBuffer: Failed to alloc fb.\n"); + return false; + } + + /* Mmap frame buffer */ + void* addr = allocator->Mmap(**handle); + if (addr == nullptr) { + printf("CreateFrameBuffer: Failed to mmap fb.\n"); + return false; + } + + printf("CreateFrameBuffer: end. handle fd: %i.\n", (*handle)->fd); + return true; +} + +void DrawBaseColor(void *image, uint32_t width, uint32_t height) +{ + uint32_t *pixels = static_cast(image); + std::vector colors = {0xff0000ff, 0xffff00ff, 0xaa00ff00, 0xff00ffaa, 0xff0f0f00}; + + static uint32_t index = 0; + if (index++ > 4) { + index = 0; + } + + for (uint32_t x = 0; x < width; x++) { + for (uint32_t y = 0; y < height; y++) { + *pixels++ = colors[index]; + } + } +} + +void SignalHandler(int signum) { + printf("Interrupt signal (%d) received.\n", signum); + + g_mainLoop.Stop(); + printf("Stop main loop done.\n"); + + for (auto& screen : g_screens) { + if (screen.second) { + delete screen.second; + } + } + + exit(signum); +} + +bool DestoryBufferHandle(BufferHandle **handle) +{ + printf("DestoryBufferHandle.\n"); + + if (handle == nullptr) { + return false; + } + + auto allocator = g_alloc_controller.GetAllocator((*handle)->usage); + if (allocator == nullptr) { + printf("CreateFrameBuffer: Failed to get buffer allocator.\n"); + return false; + } + + // Unmap buffer & Close fd + if ((*handle)->virAddr != nullptr) { + allocator->Unmap(**handle); + } + printf("DestoryBufferHandle: unmap done.\n"); + if ((*handle)->fd >= 0) { + close((*handle)->fd); + (*handle)->fd = -1; + } + printf("DestoryBufferHandle: close done.\n"); + *handle = nullptr; + return true; +} + +void Screen::OnVsync(uint32_t sequence, uint64_t timestamp, void *data) +{ + Screen *screen = static_cast(data); + if (screen == nullptr) { + printf("OnVSync: screen is null\n"); + return; + } + + // Print first frames + static int i = 0; + if (i < 3) { + printf("OnVSync: screen devId=%d, sequence=%u, timestamp=%lu\n", screen->devId, sequence, timestamp); + LOG_DEBUG("DRM Backend Test: OnVSync: screen devId=%{public}d, sequence=%{public}u, timestamp=%{public}lu", + screen->devId, sequence, timestamp); + ++i; + } + + // Do draw in main loop + g_mainLoop.RunInLoop([screen]() { + uint32_t devId = screen->devId; + + // Create two framebuffers on first frame + if (screen->firstFrame) { + if (screen->fb[0] == nullptr) { + screen->fb[0] = std::make_shared(); + CreateFrameBuffer(devId, &(screen->fb[0]->handle)); + } + if (screen->fb[1] == nullptr) { + screen->fb[1] = std::make_shared(); + CreateFrameBuffer(devId, &(screen->fb[1]->handle)); + } + } + screen->firstFrame = false; + + // Fill fb with plain color + DrawBaseColor( + screen->fb[screen->fbIdx]->handle->virAddr, + screen->fb[screen->fbIdx]->handle->width, + screen->fb[screen->fbIdx]->handle->height); + + // Set fb as screen's current buffer + int32_t fenceFd = -1; + int32_t ret = g_session.CallDisplayFunction( + devId, + &oewm::HDI::DISPLAY::HdiDisplay::SetDisplayClientBuffer, + static_cast(screen->fb[screen->fbIdx]->handle), + fenceFd); + if (ret != DISPLAY_SUCCESS) { + printf("Draw: Failed to set display client buffer, ret=%d\n", ret); + return; + } + + // Commit + ret = g_session.CallDisplayFunction( + devId, + &oewm::HDI::DISPLAY::HdiDisplay::Commit, + &fenceFd); + if (fenceFd >= 0) { + auto fence = std::make_shared(fenceFd); + } else { + auto fence = std::make_shared(-1); + } + + screen->fbIdx ^= 1; + }); +} + +static void OnHotPlug(uint32_t devId, bool connected, void *data) +{ + printf("OnHotPlug: screen devId=%d, connected=%s\n", devId, connected ? "True" : "False"); + LOG_DEBUG("DRM Backend Test: OnHotPlug: screen devId=%d, connected=%s", devId, connected ? "True" : "False"); + + // Store screen + Screen* screen = new Screen(); + screen->devId = devId; + screen->firstFrame = true; + g_screens[devId] = screen; + + // Register VSync callback + int32_t ret = g_session.CallDisplayFunction( + devId, + &oewm::HDI::DISPLAY::HdiDisplay::RegDisplayVBlankCallback, + Screen::OnVsync, + static_cast(g_screens.at(devId)) + ); + if (ret != DISPLAY_SUCCESS) { + printf("OnHotPlug: Failed to Register VSync callback, ret=%d\n", ret); + return; + } +} + +int main() +{ + signal(SIGINT, SignalHandler); + + printf("session pointer in main: %p, displays size : %lu.", &g_session, g_session.GetDisplayDevice()->GetDisplays().size()); + g_alloc_controller.Init(); + + // Register HotPlug callback + g_session.RegHotPlugCallback(OnHotPlug, nullptr); + + // Start main loop + g_mainLoop.Start(); + + return 0; +}