| // Copyright 2024 The Pigweed Authors |
| // |
| // 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 |
| // |
| // https://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 "pw_display/display.h" |
| |
| #include <array> |
| #include <utility> |
| |
| #include "gtest/gtest.h" |
| #include "pw_color/color.h" |
| |
| using pw::color::color_rgb565_t; |
| using pw::display_driver::DisplayDriver; |
| using pw::framebuffer::Framebuffer; |
| using pw::framebuffer::PixelFormat; |
| using pw::framebuffer_pool::FramebufferPool; |
| using Size = pw::geometry::Size<uint16_t>; |
| |
| namespace pw::display { |
| |
| namespace { |
| |
| constexpr size_t kMaxSavedParams = 20; |
| |
| enum class CallFunc { |
| Unset, |
| GetFramebuffer, |
| ReleaseFramebuffer, |
| WriteRow, |
| }; |
| |
| struct CallParams { |
| CallFunc call_func = CallFunc::Unset; |
| struct { |
| void* fb_data = nullptr; |
| } release_framebuffer; |
| struct { |
| size_t num_pixels = 0; |
| uint16_t row_idx = 0; |
| uint16_t col_idx = 0; |
| } write_row; |
| }; |
| |
| class TestDisplayDriver : public DisplayDriver { |
| public: |
| TestDisplayDriver(Framebuffer fb) : framebuffer_(std::move(fb)) {} |
| |
| Status Init() override { return OkStatus(); } |
| |
| void WriteFramebuffer(Framebuffer framebuffer, |
| WriteCallback write_callback) override { |
| if (next_call_param_idx_ < kMaxSavedParams) { |
| call_params_[next_call_param_idx_].call_func = |
| CallFunc::ReleaseFramebuffer; |
| call_params_[next_call_param_idx_].release_framebuffer.fb_data = |
| framebuffer.data(); |
| next_call_param_idx_++; |
| } |
| write_callback(std::move(framebuffer), OkStatus()); |
| } |
| |
| Status WriteRow(span<uint16_t> pixel_data, |
| uint16_t row_idx, |
| uint16_t col_idx) override { |
| if (next_call_param_idx_ < kMaxSavedParams) { |
| call_params_[next_call_param_idx_].call_func = CallFunc::WriteRow; |
| call_params_[next_call_param_idx_].write_row.num_pixels = |
| pixel_data.size(); |
| call_params_[next_call_param_idx_].write_row.row_idx = row_idx; |
| call_params_[next_call_param_idx_].write_row.col_idx = col_idx; |
| next_call_param_idx_++; |
| } |
| return OkStatus(); |
| } |
| |
| uint16_t GetWidth() const override { return framebuffer_.size().width; } |
| |
| uint16_t GetHeight() const override { return framebuffer_.size().height; } |
| |
| int GetNumCalls() const { |
| int count = 0; |
| for (size_t i = 0; |
| i < kMaxSavedParams && call_params_[i].call_func != CallFunc::Unset; |
| i++) { |
| count++; |
| } |
| return count; |
| } |
| |
| const CallParams& GetCall(size_t call_idx) { |
| PW_ASSERT(call_idx <= call_params_.size()); |
| |
| return call_params_[call_idx]; |
| } |
| |
| private: |
| size_t next_call_param_idx_ = 0; |
| std::array<CallParams, kMaxSavedParams> call_params_; |
| const Framebuffer framebuffer_; |
| }; |
| |
| TEST(Display, ReleaseNoResize) { |
| constexpr pw::geometry::Size<uint16_t> kFramebufferSize{2, 1}; |
| constexpr Size kDisplaySize = kFramebufferSize; |
| constexpr size_t kNumPixels = |
| kFramebufferSize.width * kFramebufferSize.height; |
| constexpr uint16_t kFramebufferRowBytes = |
| sizeof(color_rgb565_t) * kFramebufferSize.width; |
| color_rgb565_t pixel_data[kNumPixels]; |
| pw::Vector<void*, 1> pixel_buffers{pixel_data}; |
| FramebufferPool fb_pool({ |
| .fb_addr = pixel_buffers, |
| .dimensions = kFramebufferSize, |
| .row_bytes = kFramebufferRowBytes, |
| .pixel_format = PixelFormat::RGB565, |
| }); |
| |
| TestDisplayDriver test_driver(Framebuffer( |
| pixel_data, PixelFormat::RGB565, kFramebufferSize, kFramebufferRowBytes)); |
| Display display(test_driver, kDisplaySize, fb_pool); |
| Framebuffer fb = display.GetFramebuffer(); |
| EXPECT_TRUE(fb.is_valid()); |
| EXPECT_EQ(kFramebufferSize, fb.size()); |
| EXPECT_EQ(0, test_driver.GetNumCalls()); |
| |
| PW_ASSERT(display.ReleaseFramebuffer(std::move(fb)).ok()); |
| ASSERT_EQ(1, test_driver.GetNumCalls()); |
| auto call = test_driver.GetCall(0); |
| EXPECT_EQ(CallFunc::ReleaseFramebuffer, call.call_func); |
| EXPECT_EQ(pixel_data, call.release_framebuffer.fb_data); |
| } |
| |
| #if DISPLAY_RESIZE |
| TEST(Display, ReleaseSmallResize) { |
| constexpr Size kDisplaySize = {8, 4}; |
| constexpr pw::geometry::Size<uint16_t> kFramebufferSize{2, 1}; |
| constexpr size_t kNumPixels = |
| kFramebufferSize.width * kFramebufferSize.height; |
| constexpr uint16_t kFramebufferRowBytes = |
| sizeof(color_rgb565_t) * kFramebufferSize.width; |
| color_rgb565_t pixel_data[kNumPixels]; |
| pw::Vector<void*, 1> pixel_buffers{pixel_data}; |
| FramebufferPool fb_pool({ |
| .fb_addr = pixel_buffers, |
| .dimensions = kFramebufferSize, |
| .row_bytes = kFramebufferRowBytes, |
| .pixel_format = PixelFormat::RGB565, |
| }); |
| |
| TestDisplayDriver test_driver(Framebuffer( |
| pixel_data, PixelFormat::RGB565, kFramebufferSize, kFramebufferRowBytes)); |
| Display display(test_driver, kDisplaySize, fb_pool); |
| Framebuffer fb = display.GetFramebuffer(); |
| EXPECT_TRUE(fb.is_valid()); |
| EXPECT_EQ(kFramebufferSize, fb.size()); |
| EXPECT_EQ(0, test_driver.GetNumCalls()); |
| |
| PW_ASSERT(display.ReleaseFramebuffer(std::move(fb)).ok()); |
| ASSERT_EQ(4, test_driver.GetNumCalls()); |
| auto call = test_driver.GetCall(0); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(8U, call.write_row.num_pixels); |
| EXPECT_EQ(0, call.write_row.row_idx); |
| EXPECT_EQ(0, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(1); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(8U, call.write_row.num_pixels); |
| EXPECT_EQ(1, call.write_row.row_idx); |
| EXPECT_EQ(0, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(2); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(8U, call.write_row.num_pixels); |
| EXPECT_EQ(2, call.write_row.row_idx); |
| EXPECT_EQ(0, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(3); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(8U, call.write_row.num_pixels); |
| EXPECT_EQ(3, call.write_row.row_idx); |
| EXPECT_EQ(0, call.write_row.col_idx); |
| } |
| |
| TEST(Display, ReleaseWideResize) { |
| // Display width > resize buffer (80 px.) will cause two writes per row. |
| constexpr Size kDisplaySize = {90, 4}; |
| constexpr pw::geometry::Size<uint16_t> kFramebufferSize{2, 1}; |
| constexpr size_t kNumPixels = |
| kFramebufferSize.width * kFramebufferSize.height; |
| constexpr uint16_t kFramebufferRowBytes = |
| sizeof(color_rgb565_t) * kFramebufferSize.width; |
| color_rgb565_t pixel_data[kNumPixels]; |
| pw::Vector<void*, 1> pixel_buffers{pixel_data}; |
| FramebufferPool fb_pool({ |
| .fb_addr = pixel_buffers, |
| .dimensions = kFramebufferSize, |
| .row_bytes = kFramebufferRowBytes, |
| .pixel_format = PixelFormat::RGB565, |
| }); |
| |
| TestDisplayDriver test_driver(Framebuffer( |
| pixel_data, PixelFormat::RGB565, kFramebufferSize, kFramebufferRowBytes)); |
| Display display(test_driver, kDisplaySize, fb_pool); |
| Framebuffer fb = display.GetFramebuffer(); |
| EXPECT_TRUE(fb.is_valid()); |
| EXPECT_EQ(kFramebufferSize, fb.size()); |
| EXPECT_EQ(0, test_driver.GetNumCalls()); |
| |
| PW_ASSERT(display.ReleaseFramebuffer(std::move(fb)).ok()); |
| ASSERT_EQ(8, test_driver.GetNumCalls()); |
| auto call = test_driver.GetCall(0); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(80U, call.write_row.num_pixels); |
| EXPECT_EQ(0, call.write_row.row_idx); |
| EXPECT_EQ(0, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(1); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(10U, call.write_row.num_pixels); |
| EXPECT_EQ(0, call.write_row.row_idx); |
| EXPECT_EQ(80, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(2); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(80U, call.write_row.num_pixels); |
| EXPECT_EQ(1, call.write_row.row_idx); |
| EXPECT_EQ(0, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(3); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(10U, call.write_row.num_pixels); |
| EXPECT_EQ(1, call.write_row.row_idx); |
| EXPECT_EQ(80, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(4); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(80U, call.write_row.num_pixels); |
| EXPECT_EQ(2, call.write_row.row_idx); |
| EXPECT_EQ(0, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(5); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(10U, call.write_row.num_pixels); |
| EXPECT_EQ(2, call.write_row.row_idx); |
| EXPECT_EQ(80, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(6); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(80U, call.write_row.num_pixels); |
| EXPECT_EQ(3, call.write_row.row_idx); |
| EXPECT_EQ(0, call.write_row.col_idx); |
| |
| call = test_driver.GetCall(7); |
| EXPECT_EQ(CallFunc::WriteRow, call.call_func); |
| EXPECT_EQ(10U, call.write_row.num_pixels); |
| EXPECT_EQ(3, call.write_row.row_idx); |
| EXPECT_EQ(80, call.write_row.col_idx); |
| } |
| #endif // if DISPLAY_RESIZE |
| |
| } // namespace |
| |
| } // namespace pw::display |