Initial commit

This commit is contained in:
Rafał Kołucki 2026-05-25 20:38:45 +02:00
commit 85ae941c19
10 changed files with 406 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
build/

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "libraries/lvgl"]
path = libraries/lvgl
url = https://github.com/lvgl/lvgl.git

82
CMakeLists.txt Normal file
View file

@ -0,0 +1,82 @@
cmake_minimum_required(VERSION 3.16)
# -----------------------------------------------------------------------------
# Project
# -----------------------------------------------------------------------------
project(Lomo C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
# -----------------------------------------------------------------------------
# devkitPro / libctru setup
# -----------------------------------------------------------------------------
if(NOT DEFINED ENV{DEVKITPRO})
message(FATAL_ERROR "Please set DEVKITPRO in your environment.")
endif()
if(NOT DEFINED ENV{DEVKITARM})
message(FATAL_ERROR "Please set DEVKITARM in your environment.")
endif()
set(DEVKITPRO $ENV{DEVKITPRO})
set(DEVKITARM $ENV{DEVKITARM})
# Load devkitPro CMake support
include("${DEVKITPRO}/cmake/3DS.cmake")
# -----------------------------------------------------------------------------
# LVGL
# -----------------------------------------------------------------------------
cmake_policy(SET CMP0077 NEW)
#set(LV_CONF_INCLUDE_SIMPLE ON)
#set(LV_BUILD_CONF_PATH ./include/lv_conf.h)
set(CONFIG_LV_BUILD_EXAMPLES OFF)
set(CONFIG_LV_BUILD_DEMOS OFF)
add_subdirectory(libraries/lvgl)
# -----------------------------------------------------------------------------
# Target
# -----------------------------------------------------------------------------
add_executable(${PROJECT_NAME}
source/main.c
source/lv_ctr_disp.c
)
target_include_directories(${PROJECT_NAME} PRIVATE
include
)
# -----------------------------------------------------------------------------
# Libraries
# -----------------------------------------------------------------------------
target_link_libraries(${PROJECT_NAME}
lvgl
ctru
citro2d
citro3d
m
)
# -----------------------------------------------------------------------------
# 3DS metadata
# -----------------------------------------------------------------------------
set_target_properties(${PROJECT_NAME} PROPERTIES
OUTPUT_NAME "lomo"
)
# Generates:
# .3dsx
# .smdh
# .elf
ctr_generate_smdh(
OUTPUT "${PROJECT_NAME}.smdh"
NAME "Lomo"
DESCRIPTION "Camera app for the rest of us"
AUTHOR "BluRaf"
)
ctr_create_3dsx(${PROJECT_NAME}
SMDH "${PROJECT_NAME}.smdh"
)

13
CMakePresets.json Normal file
View file

@ -0,0 +1,13 @@
{
"version": 3,
"configurePresets": [
{
"name": "3ds",
"generator": "Unix Makefiles",
"binaryDir": "build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{DEVKITPRO}/cmake/3DS.cmake"
}
}
]
}

3
build.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
cmake --preset 3ds
cmake --build build --parallel $(nproc)

63
include/lv_ctr_disp.h Normal file
View file

@ -0,0 +1,63 @@
/**
* @file lv_ctr_disp.h
*
*/
#if 1
#ifndef LV_CTR_DISP_H
#define LV_CTR_DISP_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
/*********************
* DEFINES
*********************/
// TODO: make it configurable for RGB888/RGB565
// (currently hardcoded to ARGB888, otherwise I was getting only white screen)
#define CTR_LV_COLOR_FORMAT LV_COLOR_FORMAT_ARGB8888
#define CTR_TEX_COLOR_FORMAT GPU_RGBA8
#define CTR_TR_IN_COLOR_FORMAT GX_TRANSFER_FMT_RGBA8
#define CTR_TR_OUT_COLOR_FORMAT GX_TRANSFER_FMT_RGBA8
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/* Initialize low level display driver */
void lv_ctr_disp_init(void);
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
*/
void disp_enable_update(void);
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
*/
void disp_disable_update(void);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_CTR_DISP_H*/
#endif /*Disable/Enable content*/

1
libraries/lvgl Submodule

@ -0,0 +1 @@
Subproject commit fe121205addd990871d350f252c7d40dab9789da

19
lv_conf.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef LV_CONF_H
#define LV_CONF_H
#define LV_COLOR_DEPTH 32
#define LV_USE_OS 0
#define LV_MEM_SIZE (256U * 1024U)
#define LV_USE_LOG 1
//#define LV_LOG_LEVEL LV_LOG_LEVEL_TRACE
#define LV_LOG_PRINTF 1
#define LV_USE_ASSERT_NULL 1
#define LV_USE_STDLIB_MALLOC LV_STDLIB_BUILTIN
#define LV_USE_STDLIB_STRING LV_STDLIB_BUILTIN
#define LV_USE_STDLIB_SPRINTF LV_STDLIB_BUILTIN
#endif

169
source/lv_ctr_disp.c Normal file
View file

@ -0,0 +1,169 @@
/**
* @file lv_ctr_disp.c
*
*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_ctr_disp.h"
#include <stdbool.h>
#include <3ds.h>
#include <citro3d.h>
#include <citro2d.h>
/*********************
* DEFINES
*********************/
#define BOTTOM_DISP_HOR_RES 320
#define BOTTOM_DISP_VER_RES 240
#define BOTTOM_TEX_HOR_RES 512
#define BOTTOM_TEX_VER_RES 256
#define BYTE_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(CTR_LV_COLOR_FORMAT))
#define BOTTOM_DISP_STRIDE (BOTTOM_TEX_HOR_RES * BYTE_PER_PIXEL)
#define BOTTOM_DISP_BUF_SIZE BOTTOM_DISP_STRIDE * BOTTOM_DISP_VER_RES
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void disp_init(void);
static void disp_flush(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
/* Calculate nearest power of 2 for a given size */
__attribute__ ((const)) static inline u32 tex_pot(u32 x) {
if (x < 32) return 32;
return 1 << (32 - __builtin_clz(x - 1));
}
/**********************
* STATIC VARIABLES
**********************/
C3D_RenderTarget *bottom_screen;
C3D_Tex tex;
Tex3DS_SubTexture subt3x;
LV_ATTRIBUTE_MEM_ALIGN static uint8_t * buf_1 = NULL;
LV_ATTRIBUTE_MEM_ALIGN static uint8_t * buf_2 = NULL;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_ctr_disp_init(void)
{
/*-------------------------
* Initialize your display
* -----------------------*/
disp_init();
/*------------------------------------
* Create a display and set a flush_cb
* -----------------------------------*/
lv_display_t * bottom_disp = lv_display_create(BOTTOM_DISP_HOR_RES, BOTTOM_DISP_VER_RES);
lv_display_set_color_format(bottom_disp, CTR_LV_COLOR_FORMAT);
lv_display_set_flush_cb(bottom_disp, disp_flush);
lv_display_set_default(bottom_disp);
/* Two buffers for double buffering. */
buf_1 = linearAlloc(BOTTOM_DISP_BUF_SIZE);
buf_2 = linearAlloc(BOTTOM_DISP_BUF_SIZE);
lv_display_set_buffers_with_stride(bottom_disp, buf_1, buf_2, BOTTOM_DISP_BUF_SIZE, BOTTOM_DISP_STRIDE, LV_DISPLAY_RENDER_MODE_FULL);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
C2D_Init(C2D_DEFAULT_MAX_OBJECTS);
C2D_Prepare();
bottom_screen = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT);
if(0 == C3D_TexInit(&tex, BOTTOM_TEX_HOR_RES, BOTTOM_TEX_VER_RES, CTR_TEX_COLOR_FORMAT)) puts("Could not create main texture.");
memset(tex.data, 0xFF, tex.size);
C3D_TexFlush(&tex);
tex.border = 0xFFFFFFFF;
C3D_TexSetWrap(&tex, GPU_CLAMP_TO_BORDER, GPU_CLAMP_TO_BORDER);
subt3x.width = BOTTOM_DISP_HOR_RES;
subt3x.height = BOTTOM_DISP_VER_RES;
subt3x.left = 0.0f;
subt3x.top = 1.0f;
subt3x.right = (BOTTOM_DISP_HOR_RES/(float)tex.width);
subt3x.bottom = (1.0f - BOTTOM_DISP_VER_RES/(float)tex.height);
C3D_TexSetFilter(&tex, GPU_NEAREST, GPU_NEAREST);
}
volatile bool disp_flush_enabled = true;
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
*/
void disp_enable_update(void)
{
disp_flush_enabled = true;
}
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
*/
void disp_disable_update(void)
{
disp_flush_enabled = false;
}
/*Flush the content of the internal buffer the specific area on the display.
*`px_map` contains the rendered image as raw pixel map and it should be copied to `area` on the display.
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_display_flush_ready()' has to be called when it's finished.*/
static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{
if (lv_display_flush_is_last(disp_drv)) {
GSPGPU_FlushDataCache(px_map, tex.width * tex.height * 4);
u32 dim = GX_BUFFER_DIM(tex.width, tex.height);
C3D_SyncDisplayTransfer((u32 *)px_map, dim, (u32*)tex.data, dim,
(GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_RAW_COPY(0) |
GX_TRANSFER_IN_FORMAT(CTR_TR_IN_COLOR_FORMAT) | GX_TRANSFER_OUT_FORMAT(CTR_TR_OUT_COLOR_FORMAT) |
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO))
);
// start frame, clear targets
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
C2D_TargetClear(bottom_screen, C2D_Color32f(0, 0, 0, 1));
C2D_ImageTint tint;
C2D_PlainImageTint(&tint, C2D_Color32(255,255,255,255), 1.0f);
gspWaitForVBlank();
C2D_SceneBegin(bottom_screen);
C2D_Image img = { &tex, &subt3x };
C2D_DrawImageAt(img, 0, 0, 0.6f, &tint, 1.f, 1.f);
C3D_FrameEnd(0);
}
lv_display_flush_ready(disp_drv);
}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif

52
source/main.c Normal file
View file

@ -0,0 +1,52 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <3ds.h>
#include "lvgl.h"
#include "lv_ctr_disp.h"
long unsigned int sys_tick(void)
{
long unsigned int tick = (long unsigned int)osGetTime();
return tick;
}
void sys_init(void)
{
gfxInitDefault();
consoleInit(GFX_TOP, NULL);
}
void sys_free(void)
{
gfxExit();
}
int main(int argc, char* argv[])
{
sys_init();
lv_init();
lv_ctr_disp_init();
lv_tick_set_cb(sys_tick);
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "Hello world");
lv_obj_center(label);
lv_obj_invalidate(lv_screen_active());
// Main loop
while (aptMainLoop())
{
lv_timer_handler();
hidScanInput();
u32 kDown = hidKeysDown();
if (kDown & KEY_START)
break; // break in order to return to hbmenu
}
sys_free();
return 0;
}