├── .vscode
├── settings.json
├── launch.json
├── c_cpp_properties.json
└── tasks.json
├── .gitignore
├── meson.build
├── README.md
├── include
├── nvjpg.hpp
└── nvjpg
│ ├── nv
│ ├── ctrl.hpp
│ ├── registers.hpp
│ ├── ioctl_types.h
│ ├── cmdbuf.hpp
│ ├── channel.hpp
│ └── map.hpp
│ ├── bitstream.hpp
│ ├── utils.hpp
│ ├── decoder.hpp
│ ├── image.hpp
│ └── surface.hpp
├── lib
├── surface.cpp
├── image.cpp
└── decoder.cpp
├── examples
├── render-rgb-downscale.cpp
├── render-nx.cpp
├── render-rgb.cpp
├── bitmap.hpp
├── render-icons.cpp
├── render-yuv.cpp
└── render-deko3d.cpp
├── Makefile
└── LICENSE
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/build": true,
4 | "**/out": true,
5 | },
6 | "files.associations": {
7 | "**/c++/**": "cpp"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.d
2 | *.o
3 | *.map
4 |
5 | *.elf
6 | *.a
7 | *.so*
8 |
9 | *.nso
10 | *.nsp
11 | *.nro
12 | *.nacp
13 | *.npdm
14 | *.dksh
15 |
16 | *.ini
17 | *.log
18 | *.txt
19 | *.bin
20 |
21 | *.png
22 | *.jpg
23 | *.bin
24 | *.rgba
25 | *.yuv
26 | *.log
27 | *.diff
28 | *.txt
29 |
30 | **/build/
31 | **/release/
32 | **/debug/
33 | **/out/
34 | **/data/
35 | **/misc/
36 | **/docs/
37 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Debug (gdb)",
6 | "type": "cppdbg",
7 | "request": "launch",
8 | "preLaunchTask": "all",
9 | "externalConsole": false,
10 | "program": "${workspaceRoot}/build/render-rgb",
11 | "cwd": "${workspaceRoot}",
12 | "args": [
13 | "data/test.jpg",
14 | ],
15 | "environment": [
16 | { "name": "DISPLAY", "value": ":0" },
17 | ],
18 | "MIMode": "gdb",
19 | "miDebuggerPath": "/usr/bin/gdb",
20 | "setupCommands": [
21 | {
22 | "description": "Enable pretty-printing for gdb",
23 | "text": "-enable-pretty-printing",
24 | "ignoreFailures": true
25 | }
26 | ]
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('nvjpg', ['c', 'cpp'], version: '1.1.0',
2 | default_options: [
3 | 'buildtype=release',
4 | 'default_library=static',
5 | 'b_ndebug=if-release',
6 | 'b_lto=false',
7 | 'strip=false',
8 | 'warning_level=2',
9 | 'c_std=gnu11',
10 | 'cpp_std=gnu++20',
11 | ],
12 | )
13 |
14 | nvj_inc = include_directories('include')
15 |
16 | nvj_src = files(
17 | 'lib/decoder.cpp',
18 | 'lib/image.cpp',
19 | 'lib/surface.cpp',
20 | )
21 |
22 | nvj_lib = library('oss-nvjpg', nvj_src, include_directories: nvj_inc)
23 |
24 | nvj_dep = declare_dependency(include_directories: nvj_inc, link_with: nvj_lib)
25 |
26 | ex1 = executable('render-rgb',
27 | 'examples/render-rgb.cpp',
28 | dependencies: [nvj_dep, dependency('SDL2', required: true)],
29 | build_by_default: false,
30 | )
31 |
32 | ex2 = executable('render-rgb-downscale',
33 | 'examples/render-rgb-downscale.cpp',
34 | dependencies: [nvj_dep, dependency('SDL2', required: true)],
35 | build_by_default: false,
36 | )
37 |
38 | ex3 = executable('render-yuv',
39 | 'examples/render-yuv.cpp',
40 | dependencies: [nvj_dep, dependency('SDL2', required: true)],
41 | build_by_default: false,
42 | )
43 |
44 | alias_target('examples', ex1, ex2, ex3)
45 |
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Linux",
5 | "compileCommands": "build/compile_commands.json",
6 | "compilerPath": "/usr/bin/gcc",
7 | "cStandard": "gnu11",
8 | "cppStandard": "gnu++20",
9 | "intelliSenseMode": "gcc-arm64"
10 | },
11 | {
12 | "name": "DKP Aarch64",
13 | "includePath": [
14 | "${workspaceFolder}/include",
15 |
16 | "~/Bureau/Switch/libnx/nx/source/**",
17 | "~/Bureau/Switch/libnx/nx/include/**",
18 |
19 | "${DEVKITPRO}/libnx/include",
20 | "${DEVKITPRO}/portlibs/switch/include",
21 |
22 | "${DEVKITPRO}/devkitA64/aarch64-none-elf/include/**",
23 | "${DEVKITPRO}/devkitA64/lib/gcc/aarch64-none-elf/*/include"
24 | ],
25 | "defines": [
26 | "__aarch64__",
27 | "__SWITCH__",
28 | "DEBUG"
29 | ],
30 | "compilerPath": "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc",
31 | "cStandard": "gnu11",
32 | "cppStandard": "gnu++20",
33 | "intelliSenseMode": "gcc-arm64"
34 | }
35 | ],
36 | "version": 4
37 | }
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # oss-nvjpg
2 |
3 | The Tegra X1 provides a hardware module dedicated to JPEG encoding/decoding (NVJPG).
4 |
5 | Using this library, images can be rendered to an RGB or YUV (triplanar) surface. YUV➝RGB conversion is handled in hardware. In addition, images can be downscaled to up to 8, also done in hardware.
6 |
7 | Note: only baseline JPEGs are supported. Progressive and arithmetic coded files will return an error.
8 |
9 | ### Performance
10 |
11 | Decoding times are largely faster than those obtained by software rendering, even with SIMD optimizations. The results below were obtained on an Aarch64 Cortex-A57 clocked at max 2.091GHz, by decoding the same image to an RGB surface, averaging 1000 iterations:
12 | | Library | 70Kib 720x1080 | 1.6Mib 3200x1800 |
13 | | --- |:---:|:---:|
14 | | NVJPG | 2.036ms | 15.997ms |
15 | | libjpeg-turbo | 6.127ms | 66.341ms |
16 | | stb_image (with NEON routines) | 16.512ms | 158.474ms |
17 | | Python-pillow | 11.688ms | 114.918ms |
18 | | nanoJPEG | 48.299ms | 531.575ms |
19 |
20 | ## Building
21 | Requires C++20 support.
22 |
23 | ### Linux
24 | ```sh
25 | meson build && meson compile -C build
26 | ```
27 | Additionally, run `meson compile -C build examples` to build the examples.
28 |
29 | ### devkitA64
30 | ```sh
31 | make -j$(nproc)
32 | ```
33 | Additionally, run `make examples` to build the examples.
34 |
35 | ## Credits
36 | - The [Ryujinx](https://github.com/Ryujinx/Ryujinx) project for extensive documentation of the Host1x interface
37 |
--------------------------------------------------------------------------------
/include/nvjpg.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | namespace nj {
28 |
29 | #ifdef __SWITCH__
30 | extern "C" std::uint32_t __nx_applet_type;
31 | #endif
32 |
33 | [[maybe_unused]]
34 | static Result initialize() {
35 | #ifdef __SWITCH__
36 | // Override the applet type, which controls what flavour of nvdrv gets initialized
37 | // To get access to /dev/nvhost-nvjpg, we need nvdrv:a/s/t
38 | auto saved_applet_type = std::exchange(__nx_applet_type, AppletType_LibraryApplet);
39 | NJ_SCOPEGUARD([&saved_applet_type] { __nx_applet_type = saved_applet_type; });
40 |
41 | NJ_TRY_RET(nvInitialize());
42 | NJ_TRY_RET(nj::NvMap::initialize());
43 | NJ_TRY_RET(nj::NvHostCtrl::initialize());
44 |
45 | NJ_TRY_RET(mmuInitialize());
46 | #else
47 | NJ_TRY_ERRNO(nj::NvMap::initialize());
48 | NJ_TRY_ERRNO(nj::NvHostCtrl::initialize());
49 | #endif
50 | return 0;
51 | }
52 |
53 | [[maybe_unused]]
54 | static Result finalize() {
55 | #ifdef __SWITCH__
56 | NJ_TRY_RET(nj::NvMap::finalize());
57 | NJ_TRY_RET(nj::NvHostCtrl::finalize());
58 | nvExit();
59 |
60 | mmuExit();
61 | #else
62 | NJ_TRY_ERRNO(nj::NvMap::finalize());
63 | NJ_TRY_ERRNO(nj::NvHostCtrl::finalize());
64 | #endif
65 | return 0;
66 | }
67 |
68 | } // namespace nj
69 |
--------------------------------------------------------------------------------
/include/nvjpg/nv/ctrl.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 |
22 | #ifdef __SWITCH__
23 | # include
24 | #else
25 | # include
26 | # include
27 | # include
28 | # include
29 | # include
30 | #endif
31 |
32 | #include
33 | #include
34 |
35 | namespace nj {
36 |
37 | class NvHostCtrl {
38 | public:
39 | static Result initialize() {
40 | #ifdef __SWITCH__
41 | return nvFenceInit();
42 | #else
43 | return NvHostCtrl::nvhostctrl_fd = ::open("/dev/nvhost-ctrl", O_RDWR | O_SYNC | O_CLOEXEC);
44 | #endif
45 | }
46 |
47 | static Result finalize() {
48 | #ifdef __SWITCH__
49 | nvFenceExit();
50 | return 0;
51 | #else
52 | return ::close(NvHostCtrl::nvhostctrl_fd);
53 | #endif
54 | }
55 |
56 | #ifdef __SWITCH__
57 | static Result wait(NvFence fence, std::int32_t timeout) {
58 | return nvFenceWait(&fence, timeout);
59 | }
60 | #else
61 | static Result wait(nvhost_ctrl_fence fence, std::int32_t timeout) {
62 | nvhost_ctrl_syncpt_waitex_args args = {
63 | .id = fence.id,
64 | .thresh = fence.value,
65 | .timeout = timeout,
66 | .value = 0,
67 | };
68 |
69 | return ::ioctl(NvHostCtrl::nvhostctrl_fd, NVHOST_IOCTL_CTRL_SYNCPT_WAITEX, &args);
70 | }
71 | #endif
72 |
73 | private:
74 | #ifndef __SWITCH__
75 | static inline int nvhostctrl_fd = 0;
76 | #endif
77 | };
78 |
79 | } // namespace nj
80 |
81 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "gen",
6 | "type": "shell",
7 | "command": "meson",
8 | "args": [
9 | "build",
10 | "--buildtype",
11 | "debug",
12 | "-Db_lto=false"
13 | ],
14 | "presentation": {
15 | "panel": "shared",
16 | "reveal": "never"
17 | },
18 | "problemMatcher": []
19 | },
20 | {
21 | "label": "all",
22 | "type": "shell",
23 | "command": "ninja",
24 | "args": [
25 | "all",
26 | "examples",
27 | "-j$(nproc)",
28 | ],
29 | "options": {
30 | "cwd": "${workspaceFolder}/build",
31 | "env": {
32 | "LANG": "en",
33 | }
34 | },
35 | "presentation": {
36 | "reveal": "always",
37 | "panel": "shared",
38 | "clear": true
39 | },
40 | "problemMatcher": {
41 | "base": "$gcc",
42 | "fileLocation": ["relative", "${workspaceFolder}/build"]
43 | },
44 | "group": {
45 | "kind": "build",
46 | "isDefault": true
47 | }
48 | },
49 | {
50 | "label": "clean",
51 | "type": "shell",
52 | "command": "ninja",
53 | "args": [
54 | "clean"
55 | ],
56 | "options": {
57 | "cwd": "${workspaceFolder}/build",
58 | },
59 | "presentation": {
60 | "panel": "shared",
61 | "reveal": "never"
62 | },
63 | "problemMatcher": []
64 | },
65 | {
66 | "label": "run",
67 | "dependsOn": [
68 | "all",
69 | ],
70 | "type": "shell",
71 | "command": "${workspaceFolder}/build/render-rgb",
72 | "args": [
73 | "data/test.jpg",
74 | ],
75 | "options": {
76 | "env": {
77 | "DISPLAY": ":0",
78 | }
79 | },
80 | "presentation": {
81 | "reveal": "always",
82 | "panel": "shared",
83 | "clear": true
84 | },
85 | "problemMatcher": []
86 | }
87 | ]
88 | }
89 |
--------------------------------------------------------------------------------
/include/nvjpg/bitstream.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | namespace nj {
26 |
27 | class Bitstream {
28 | public:
29 | Bitstream(const std::vector &data): data(data), cur(data.begin()) { }
30 |
31 | template
32 | T get() {
33 | if (this->cur + sizeof(T) >= this->data.end()) {
34 | this->cur = this->data.end();
35 | return {};
36 | }
37 | auto tmp = *reinterpret_cast(this->cur.base());
38 | this->cur += sizeof(T);
39 | return tmp;
40 | }
41 |
42 | template
43 | T get_be() {
44 | if constexpr (sizeof(T) == 1)
45 | return this->get();
46 | else if constexpr (sizeof(T) == 2)
47 | return __builtin_bswap16(this->get());
48 | else if constexpr (sizeof(T) == 4)
49 | return __builtin_bswap32(this->get());
50 | else if constexpr (sizeof(T) == 8)
51 | return __builtin_bswap64(this->get());
52 | return {};
53 | }
54 |
55 | void skip(std::size_t size) {
56 | this->cur += size;
57 | }
58 |
59 | void rewind(std::size_t size) {
60 | this->cur -= size;
61 | }
62 |
63 | bool empty() const {
64 | return this->cur >= this->data.end();
65 | }
66 |
67 | std::size_t size() const {
68 | return this->data.end() - this->cur;
69 | }
70 |
71 | auto current() const {
72 | return this->cur;
73 | }
74 |
75 | private:
76 | const std::vector &data;
77 | std::vector::const_iterator cur;
78 | };
79 |
80 | } // namespace nj
81 |
--------------------------------------------------------------------------------
/include/nvjpg/utils.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 | #include
23 |
24 | #define _NJ_CAT(x, y) x ## y
25 | #define NJ_CAT(x, y) _NJ_CAT(x, y)
26 | #define _NJ_STR(x) #x
27 | #define NJ_STR(x) _NJ_STR(x)
28 |
29 | #define NJ_ANONYMOUS_VAR NJ_CAT(var, __COUNTER__)
30 |
31 | #define NJ_SCOPEGUARD(f) auto NJ_ANONYMOUS_VAR = ::nj::ScopeGuard(f)
32 |
33 | #ifndef __SWITCH__
34 | typedef int Result;
35 | #endif
36 |
37 | #define NJ_TRY_RET(expr) \
38 | if (auto _rc = (expr); _rc) \
39 | return _rc;
40 |
41 | #define NJ_TRY_ERRNO(expr) \
42 | if ((expr); errno) \
43 | return errno;
44 |
45 | #define NJ_UNUSED(...) ::nj::variadic_unused(__VA_ARGS__)
46 |
47 | namespace nj {
48 |
49 | template
50 | consteval void variadic_unused(Args &&...args) {
51 | (static_cast(args), ...);
52 | }
53 |
54 | template
55 | struct [[nodiscard]] ScopeGuard {
56 | ScopeGuard(F &&f): f(std::move(f)) { }
57 |
58 | ScopeGuard(const ScopeGuard &) = delete;
59 | ScopeGuard(ScopeGuard &&) = delete;
60 | ScopeGuard &operator =(const ScopeGuard &) = delete;
61 | ScopeGuard &operator =(ScopeGuard &&) = delete;
62 |
63 | ~ScopeGuard() {
64 | this->f();
65 | }
66 |
67 | private:
68 | F f;
69 | };
70 |
71 | constexpr auto bit(std::unsigned_integral auto bits) {
72 | return 1 << bits;
73 | }
74 |
75 | constexpr auto mask(std::unsigned_integral auto bits) {
76 | return (1 << bits) - 1;
77 | }
78 |
79 | template
80 | constexpr T align_down(T val, T align) {
81 | return val & ~(align - 1);
82 | }
83 |
84 | template
85 | constexpr T align_up(T val, T align) {
86 | return (val + align - 1) & ~(align - 1);
87 | }
88 |
89 | template
90 | constexpr bool is_aligned(T val, T align) {
91 | return !(val & (align - 1));
92 | }
93 |
94 | } // namespace nj
95 |
--------------------------------------------------------------------------------
/lib/surface.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #include
19 |
20 | #include
21 |
22 | #include
23 |
24 | namespace nj {
25 |
26 | namespace {
27 |
28 | std::size_t compute_pitch(std::size_t width, std::size_t bpp) {
29 | return align_up(width * bpp, 0x100ul);
30 | }
31 |
32 | std::size_t compute_size(std::size_t pitch, std::size_t height) {
33 | return align_up(pitch * height, 0x20000ul);
34 | }
35 |
36 | } // namespace
37 |
38 | int Surface::allocate() {
39 | this->pitch = compute_pitch(this->width, this->get_bpp());
40 | NJ_TRY_RET(this->map.allocate(compute_size(this->pitch, this->height), 0x400, 0x1));
41 | #ifndef __SWITCH__
42 | NJ_TRY_ERRNO(this->map.map());
43 | #endif
44 | return 0;
45 | }
46 |
47 | int VideoSurface::allocate() {
48 | auto hsubsamp = 0, vsubsamp = 0;
49 | switch (this->sampling) {
50 | case SamplingFormat::S420:
51 | hsubsamp = 2, vsubsamp = 2;
52 | break;
53 | case SamplingFormat::S422:
54 | hsubsamp = 2, vsubsamp = 1;
55 | break;
56 | case SamplingFormat::S444:
57 | hsubsamp = 1, vsubsamp = 1;
58 | break;
59 | default:
60 | return EINVAL;
61 | }
62 |
63 | this->luma_pitch = compute_pitch(this->width, 1);
64 | this->chroma_pitch = compute_pitch(this->width / hsubsamp, 1);
65 |
66 | auto luma_size = compute_size(this->luma_pitch, this->height);
67 | auto chroma_size = compute_size(this->chroma_pitch, this->height / vsubsamp);
68 | NJ_TRY_RET(this->map.allocate(luma_size + 2 * chroma_size, 0x400, 0x1));
69 | #ifndef __SWITCH__
70 | NJ_TRY_ERRNO(this->map.map());
71 | #endif
72 |
73 | this->luma_data = static_cast(this->map.address());
74 | this->chromab_data = static_cast(this->map.address()) + luma_size;
75 | this->chromar_data = static_cast(this->map.address()) + luma_size + chroma_size;
76 | return 0;
77 | }
78 |
79 | } // namespace nj
80 |
--------------------------------------------------------------------------------
/examples/render-rgb-downscale.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "bitmap.hpp"
26 |
27 | // Has to be a power of two and smaller than 8
28 | #define DOWNSCALE_FACTOR 4
29 |
30 | int main(int argc, char **argv) {
31 | if (argc < 2) {
32 | std::fprintf(stderr, "Usage: %s directory\n", argv[0]);
33 | return 1;
34 | }
35 |
36 | if (auto rc = nj::initialize(); rc) {
37 | std::fprintf(stderr, "Failed to initialize library: %d: %s\n", rc, std::strerror(rc));
38 | return 1;
39 | }
40 | NJ_SCOPEGUARD([] { nj::finalize(); });
41 |
42 | nj::Decoder decoder;
43 | if (auto rc = decoder.initialize(); rc) {
44 | std::fprintf(stderr, "Failed to initialize decoder: %#x\n", rc);
45 | return rc;
46 | }
47 | NJ_SCOPEGUARD([&decoder] { decoder.finalize(); });
48 |
49 | nj::Surface surf(0x1000, 0x1000, nj::PixelFormat::BGRA);
50 | if (auto rc = surf.allocate(); rc) {
51 | std::fprintf(stderr, "Failed to allocate surface: %#x\n", rc);
52 | return 1;
53 | }
54 |
55 | std::printf("Surface pitch: %#lx, size %#lx\n", surf.pitch, surf.size());
56 |
57 | for (auto entry: std::filesystem::directory_iterator(argv[1])) {
58 | if (entry.is_directory())
59 | continue;
60 |
61 | auto path = entry.path();
62 | if (path.extension() != ".jpg" && path.extension() != ".jpeg")
63 | continue;
64 |
65 | std::puts(path.c_str());
66 |
67 | nj::Image image(path.string());
68 | if (!image.is_valid() || image.parse()) {
69 | std::perror("Invalid file");
70 | return 1;
71 | }
72 |
73 | std::printf("Image dimensions: %ux%u\n", image.width, image.height);
74 |
75 | auto start = std::chrono::system_clock::now();
76 | if (auto rc = decoder.render(image, surf, 0, DOWNSCALE_FACTOR); rc)
77 | std::fprintf(stderr, "Failed to render image: %#x (%s)\n", rc, std::strerror(errno));
78 |
79 | std::size_t read = 0;
80 | decoder.wait(surf, &read);
81 | auto time = std::chrono::system_clock::now() - start;
82 | std::printf("Rendered in %ldµs, read bytes: %lu\n",
83 | std::chrono::duration_cast(time).count(), read);
84 |
85 | nj::Bmp bmp(image.width / DOWNSCALE_FACTOR, image.height / DOWNSCALE_FACTOR);
86 | if (auto rc = bmp.write(surf, path.replace_extension(".bmp").string()); rc) {
87 | std::fprintf(stderr, "Failed to write bmp: %d\n", rc);
88 | return rc;
89 | }
90 | }
91 |
92 | return 0;
93 | }
94 |
--------------------------------------------------------------------------------
/examples/render-nx.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | extern "C" void userAppInit() {
25 | socketInitializeDefault();
26 | nxlinkStdio();
27 | }
28 |
29 | extern "C" void userAppExit() {
30 | socketExit();
31 | }
32 |
33 | static void display_image(const nj::Surface &surface) {
34 | constexpr std::size_t fb_width = 1280, fb_height = 720;
35 | Framebuffer fb;
36 | framebufferCreate(&fb, nwindowGetDefault(), fb_width, fb_height, PIXEL_FORMAT_RGBA_8888, 1);
37 | framebufferMakeLinear(&fb);
38 |
39 | auto *framebuf = static_cast(framebufferBegin(&fb, nullptr));
40 | for (std::size_t i = 0; i < std::min(fb_height, surface.height); ++i)
41 | std::copy_n(surface.data() + i * surface.pitch, std::min(surface.width, fb_width) * surface.get_bpp(), framebuf + i * fb.stride);
42 | framebufferEnd(&fb);
43 |
44 | PadState pad;
45 | padConfigureInput(1, HidNpadStyleTag_NpadHandheld);
46 | padInitializeDefault(&pad);
47 | while (appletMainLoop()) {
48 | padUpdate(&pad);
49 | if (padGetButtonsDown(&pad) & HidNpadButton_Plus)
50 | break;
51 | }
52 |
53 | framebufferClose(&fb);
54 | }
55 |
56 | int main(int argc, char **argv) {
57 | if (auto rc = nj::initialize(); rc) {
58 | std::fprintf(stderr, "Failed to initialize library: %d: %s\n", rc, std::strerror(rc));
59 | return 1;
60 | }
61 | NJ_SCOPEGUARD([] { nj::finalize(); });
62 |
63 | nj::Decoder decoder;
64 | if (auto rc = decoder.initialize(); rc) {
65 | std::fprintf(stderr, "Failed to initialize decoder: %#x\n", rc);
66 | return rc;
67 | }
68 | NJ_SCOPEGUARD([&decoder] { decoder.finalize(); });
69 |
70 | nj::Image image((argc < 2) ? "sdmc:/test.jpg" : argv[1]);
71 | if (!image.is_valid() || image.parse()) {
72 | std::perror("Invalid file");
73 | return 1;
74 | }
75 |
76 | std::printf("Image dimensions: %ux%u\n", image.width, image.height);
77 |
78 | nj::Surface surf(image.width, image.height, nj::PixelFormat::RGBA);
79 | if (auto rc = surf.allocate(); rc) {
80 | std::fprintf(stderr, "Failed to allocate surface: %#x\n", rc);
81 | return 1;
82 | }
83 |
84 | std::printf("Surface pitch: %#lx, size %#lx\n", surf.pitch, surf.size());
85 |
86 | auto start = std::chrono::system_clock::now();
87 | if (auto rc = decoder.render(image, surf, 255); rc)
88 | std::fprintf(stderr, "Failed to render image: %#x (%s)\n", rc, std::strerror(errno));
89 |
90 | std::size_t read = 0;
91 | decoder.wait(surf, &read);
92 | auto time = std::chrono::system_clock::now() - start;
93 | std::printf("Rendered in %ldµs, read bytes: %lu\n",
94 | std::chrono::duration_cast(time).count(), read);
95 |
96 | std::printf("Press + to exit\n");
97 |
98 | display_image(surf);
99 |
100 | return 0;
101 | }
102 |
--------------------------------------------------------------------------------
/include/nvjpg/decoder.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #ifdef __SWITCH__
30 | # include
31 | #endif
32 |
33 | namespace nj {
34 |
35 | class Decoder {
36 | public:
37 | struct RingEntry {
38 | NvMap cmdbuf_map, pic_info_map, read_data_map, scan_data_map;
39 | CmdBuf cmdbuf{cmdbuf_map};
40 | nvhost_ctrl_fence fence{ 0, -1u };
41 | };
42 |
43 | enum class ColorSpace {
44 | BT601, // ITUR BT-601
45 | BT709, // ITUR BT-709
46 | BT601Ex, // ITUR BT-601 extended range (16-235 -> 0-255), JFIF
47 | };
48 |
49 | constexpr static std::uint32_t class_id = 0xc0;
50 |
51 | public:
52 | ColorSpace colorspace = ColorSpace::BT601Ex;
53 |
54 | public:
55 | Result initialize(std::size_t num_ring_entries = 1, std::size_t capacity = 0x500000); // 5 Mib
56 | Result finalize();
57 |
58 | Result resize(std::size_t capacity);
59 |
60 | std::size_t capacity() const {
61 | if (this->entries.empty())
62 | return 0;
63 | return this->entries[0].scan_data_map.size();
64 | }
65 |
66 | Result render(const Image &image, Surface &surf, std::uint8_t alpha = 0, std::uint32_t downscale = 0);
67 | Result render(const Image &image, VideoSurface &surf, std::uint32_t downscale = 0);
68 |
69 | Result wait(const SurfaceBase &surf, std::size_t *num_read_bytes = nullptr, std::int32_t timeout_us = -1);
70 |
71 | Result wait(auto &&...surfs) requires requires (decltype(surfs) ...args) { (args.width, ...); } {
72 | return (this->wait(surfs, nullptr, -1) | ...);
73 | }
74 |
75 | // In Hz
76 | std::uint32_t get_clock_rate() const {
77 | std::uint32_t rate = 0;
78 | #ifdef __SWITCH__
79 | std::uint32_t tmp = 0;
80 | if (auto rc = mmuRequestGet(&this->request, &tmp); R_SUCCEEDED(rc))
81 | rate = tmp;
82 | #else
83 | this->channel.get_clock_rate(Decoder::class_id, rate);
84 | #endif
85 | return rate;
86 | }
87 |
88 | // In Hz
89 | Result set_clock_rate(std::uint32_t rate) const {
90 | #ifdef __SWITCH__
91 | return mmuRequestSetAndWait(&this->request, rate, -1u);
92 | #else
93 | return this->channel.set_clock_rate(Decoder::class_id, rate);
94 | #endif
95 | }
96 |
97 | private:
98 | RingEntry &get_ring_entry() const;
99 |
100 | NvjpgPictureInfo *build_picture_info_common(RingEntry &entry, const Image &image, std::uint32_t downscale);
101 |
102 | Result render_common(RingEntry &entry, const Image &image, SurfaceBase &surf);
103 |
104 | private:
105 | NvChannel channel;
106 | std::vector entries;
107 | std::vector::iterator next_entry;
108 |
109 | #ifdef __SWITCH__
110 | MmuRequest request;
111 | #endif
112 | };
113 |
114 | } // namespace nj
115 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ifeq ($(strip $(DEVKITPRO)),)
2 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro")
3 | endif
4 |
5 | TOPDIR ?= $(CURDIR)
6 |
7 | VERSION = 1.1.0
8 | COMMIT = $(shell git rev-parse --short HEAD)
9 |
10 | # -----------------------------------------------
11 |
12 | TARGET = nvjpg
13 | EXTENSION = a
14 | OUT = out
15 | BUILD = build
16 | SOURCES = lib
17 | INCLUDES = include
18 | CUSTOM_LIBS =
19 | ROMFS =
20 | EXAMPLES = examples/render-nx.cpp examples/render-icons.cpp examples/render-deko3d.cpp
21 |
22 | DEFINES = __SWITCH__ VERSION=\"$(VERSION)\" COMMIT=\"$(COMMIT)\"
23 | ARCH = -march=armv8-a+crc+crypto+simd -mtune=cortex-a57 -mtp=soft -fpie
24 | FLAGS = -Wall -pipe -g -O2 -ffunction-sections -fdata-sections
25 | CFLAGS = -std=gnu11
26 | CXXFLAGS = -std=gnu++20 -fno-rtti -fno-exceptions
27 | ASFLAGS =
28 | LDFLAGS = -Wl,-pie -specs=$(DEVKITPRO)/libnx/switch.specs -g
29 | LINKS = -lnx
30 |
31 | PREFIX = aarch64-none-elf-
32 | CC = $(PREFIX)gcc
33 | CXX = $(PREFIX)g++
34 | AR = $(PREFIX)ar
35 | LD = $(PREFIX)g++
36 |
37 | # -----------------------------------------------
38 |
39 | export PATH := $(DEVKITPRO)/tools/bin:$(DEVKITPRO)/devkitA64/bin:$(PORTLIBS)/bin:$(PATH)
40 |
41 | PORTLIBS = $(DEVKITPRO)/portlibs/switch
42 | LIBNX = $(DEVKITPRO)/libnx
43 | LIBS = $(LIBNX) $(PORTLIBS)
44 |
45 | # -----------------------------------------------
46 |
47 | CFILES = $(shell find $(SOURCES) -name *.c)
48 | CPPFILES = $(shell find $(SOURCES) -name *.cpp)
49 | SFILES = $(shell find $(SOURCES) -name *.s -or -name *.S)
50 | OFILES = $(CFILES:%=$(BUILD)/%.o) $(CPPFILES:%=$(BUILD)/%.o) $(SFILES:%=$(BUILD)/%.o)
51 | DFILES = $(OFILES:.o=.d)
52 |
53 | LIB_TARGET = $(if $(OUT:=), $(OUT)/lib$(TARGET).$(EXTENSION), .$(OUT)/lib$(TARGET).$(EXTENSION))
54 | DEFINE_FLAGS = $(addprefix -D,$(DEFINES))
55 | INCLUDE_FLAGS = $(addprefix -I$(CURDIR)/,$(INCLUDES)) $(foreach dir,$(CUSTOM_LIBS),-I$(CURDIR)/$(dir)/include) \
56 | $(foreach dir,$(filter-out $(CUSTOM_LIBS),$(LIBS)),-I$(dir)/include)
57 | LIB_FLAGS = $(foreach dir,$(LIBS),-L$(dir)/lib)
58 |
59 |
60 | EXAMPLES_TARGET = $(if $(OUT:=), $(patsubst %, $(OUT)/%.nro, $(basename $(EXAMPLES))), \
61 | $(patsubst %, .$(OUT)/%.nro, $(basename $(EXAMPLES))))
62 | EXAMPLES_OFILES = $(EXAMPLES:%=$(BUILD)/%.o)
63 | EXAMPLES_DFILES = $(EXAMPLES_OFILES:.o=.d)
64 |
65 | # -----------------------------------------------
66 |
67 | .SUFFIXES:
68 |
69 | .PHONY: all clean examples
70 |
71 | all: $(LIB_TARGET)
72 | @:
73 |
74 | examples: $(EXAMPLES_TARGET) $(EXAMPLES_OFILES)
75 | @:
76 |
77 | $(OUT)/%.nro: $(BUILD)/%.cpp.o $(LIB_TARGET)
78 | @mkdir -p $(dir $@)
79 | @echo " NRO " $@
80 | @$(LD) $(ARCH) $^ -L $(OUT) -L $(DEVKITPRO)/libnx/lib -ldeko3d -l nvjpg -l nx -Wl,-pie -specs=$(DEVKITPRO)/libnx/switch.specs -o $(@:.nro=.elf)
81 | @nacptool --create $(notdir $(@:.nro=)) averne 0.0.0 $(@:.nro=.nacp)
82 | @elf2nro $(@:.nro=.elf) $@ --nacp=$(@:.nro=.nacp) > /dev/null
83 |
84 | $(LIB_TARGET): $(OFILES)
85 | @echo " AR " $@
86 | @mkdir -p $(dir $@)
87 | @$(AR) rc $@ $^
88 |
89 | $(BUILD)/%.c.o: %.c
90 | @echo " CC " $@
91 | @mkdir -p $(dir $@)
92 | @$(CC) -MMD -MP $(ARCH) $(FLAGS) $(CFLAGS) $(DEFINE_FLAGS) $(INCLUDE_FLAGS) -c $(CURDIR)/$< -o $@
93 |
94 | $(BUILD)/%.cpp.o: %.cpp
95 | @echo " CXX " $@
96 | @mkdir -p $(dir $@)
97 | @$(CXX) -MMD -MP $(ARCH) $(FLAGS) $(CXXFLAGS) $(DEFINE_FLAGS) $(INCLUDE_FLAGS) -c $(CURDIR)/$< -o $@
98 |
99 | $(BUILD)/%.s.o: %.s %.S
100 | @echo " AS " $@
101 | @mkdir -p $(dir $@)
102 | @$(AS) -MMD -MP -x assembler-with-cpp $(ARCH) $(FLAGS) $(ASFLAGS) $(INCLUDE_FLAGS) -c $(CURDIR)/$< -o $@
103 |
104 | clean:
105 | @echo Cleaning...
106 | @rm -rf $(BUILD) $(OUT)
107 |
108 | -include $(DFILES) $(EXAMPLES_DFILES)
109 |
--------------------------------------------------------------------------------
/include/nvjpg/image.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | #include
29 | #include
30 |
31 | namespace nj {
32 |
33 | enum class JpegMarker: std::uint8_t {
34 | Sof0 = 0xc0,
35 | Sof1 = 0xc1,
36 | Sof2 = 0xc2,
37 | Sof15 = 0xcf,
38 |
39 | Dht = 0xc4,
40 | Soi = 0xd8,
41 | Eoi = 0xd9,
42 | Sos = 0xda,
43 | Dqt = 0xdb,
44 | Dri = 0xdd,
45 |
46 | App0 = 0xe0,
47 | App15 = 0xef,
48 |
49 | Magic = 0xff,
50 | };
51 |
52 | struct JpegSegmentHeader {
53 | JpegMarker magic;
54 | JpegMarker marker;
55 | std::uint16_t size;
56 | };
57 |
58 | class Image {
59 | public:
60 | struct Component {
61 | std::uint8_t sampling_horiz, sampling_vert;
62 | std::uint8_t quant_table_id;
63 | std::uint8_t hm_ac_table_id, hm_dc_table_id;
64 | };
65 |
66 | struct QuantizationTable {
67 | std::array table;
68 | };
69 |
70 | struct HuffmanTable {
71 | std::array codes;
72 | std::array symbols;
73 | };
74 |
75 | public:
76 | std::uint16_t width = 0;
77 | std::uint16_t height = 0;
78 | std::uint8_t mcu_size_horiz = 0;
79 | std::uint8_t mcu_size_vert = 0;
80 | bool progressive = false;
81 | std::uint8_t num_components = 0; // 1 (grayscale) and 3 (YUV) supported
82 | std::uint8_t sampling_precision = 0; // 8 and 12-bit precision supported
83 | SamplingFormat sampling;
84 | std::uint16_t restart_interval = 0;
85 | std::uint8_t spectral_selection_lo = 0;
86 | std::uint8_t spectral_selection_hi = 0;
87 |
88 | std::array comp_idx = {};
89 | std::array components = {};
90 | std::array quant_tables = {};
91 | std::array hm_ac_tables = {};
92 | std::array hm_dc_tables = {};
93 |
94 | std::uint8_t quant_mask = 0, hm_ac_mask = 0, hm_dc_mask = 0;
95 |
96 | public:
97 | Image() = default;
98 | Image(std::shared_ptr> data): data(data) { }
99 | Image(int fd);
100 | Image(FILE *fp): Image(fileno(fp)) { }
101 | Image(std::string_view path): Image(::open(path.data(), O_RDONLY)) { }
102 |
103 | bool is_valid() const {
104 | return this->valid;
105 | }
106 |
107 | int parse();
108 |
109 | std::span get_scan_data() const {
110 | return std::span(this->data->begin() + this->scan_offset, this->data->size() - this->scan_offset);
111 | }
112 |
113 | private:
114 | JpegSegmentHeader find_next_segment(Bitstream &bs);
115 |
116 | int parse_app(JpegSegmentHeader seg, Bitstream &bs);
117 | int parse_sof(JpegSegmentHeader seg, Bitstream &bs);
118 | int parse_dqt(JpegSegmentHeader seg, Bitstream &bs);
119 | int parse_dht(JpegSegmentHeader seg, Bitstream &bs);
120 | int parse_dri(JpegSegmentHeader seg, Bitstream &bs);
121 | int parse_sos(JpegSegmentHeader seg, Bitstream &bs);
122 |
123 | private:
124 | bool valid = true;
125 | std::uint32_t scan_offset = 0;
126 | std::shared_ptr> data;
127 | };
128 |
129 | } // namespace nj
130 |
--------------------------------------------------------------------------------
/examples/render-rgb.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | static void display_image(const nj::Surface &surface) {
25 | if (SDL_Init(SDL_INIT_VIDEO) < 0) {
26 | std::fprintf(stderr, "Could not initialize sdl2: %s\n", SDL_GetError());
27 | return;
28 | }
29 | NJ_SCOPEGUARD([] { SDL_Quit(); });
30 |
31 | auto *window = SDL_CreateWindow("nvjpg",
32 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
33 | 1080, 720, SDL_WINDOW_SHOWN);
34 | if (!window) {
35 | std::fprintf(stderr, "Could not create window: %s\n", SDL_GetError());
36 | return;
37 | }
38 | NJ_SCOPEGUARD([&window] { SDL_DestroyWindow(window); });
39 |
40 | auto *screen_surf = SDL_GetWindowSurface(window);
41 | if (!screen_surf) {
42 | std::fprintf(stderr, "Could not get window surface: %s\n", SDL_GetError());
43 | return;
44 | }
45 | NJ_SCOPEGUARD([&screen_surf] { SDL_FreeSurface(screen_surf); });
46 |
47 | auto *surf = SDL_CreateRGBSurfaceWithFormatFrom(const_cast(surface.data()),
48 | surface.width, surface.height, surface.get_bpp() * 8, surface.pitch, SDL_PIXELFORMAT_RGBA32);
49 | if (!surf) {
50 | std::fprintf(stderr, "Failed to create image surface: %s\n", SDL_GetError());
51 | return;
52 | }
53 | NJ_SCOPEGUARD([&surf] { SDL_FreeSurface(surf); });
54 |
55 | auto *converted_surf = SDL_ConvertSurface(surf, screen_surf->format, 0);
56 | if (!converted_surf) {
57 | std::fprintf(stderr, "Could not convert surface: %s\n", SDL_GetError());
58 | return;
59 | }
60 | NJ_SCOPEGUARD([&converted_surf] { SDL_FreeSurface(converted_surf); });
61 |
62 | SDL_BlitSurface(converted_surf, nullptr, screen_surf, nullptr);
63 | SDL_UpdateWindowSurface(window);
64 |
65 | SDL_Event e;
66 | while (true) {
67 | if (!SDL_WaitEvent(&e)) {
68 | std::fprintf(stderr, "Error while waiting on an event: %s\n", SDL_GetError());
69 | return;
70 | }
71 |
72 | if (e.type == SDL_QUIT)
73 | break;
74 | }
75 | }
76 |
77 | int main(int argc, char **argv) {
78 | if (argc < 2) {
79 | std::fprintf(stderr, "Usage: %s jpg\n", argv[0]);
80 | return 1;
81 | }
82 |
83 | if (auto rc = nj::initialize(); rc) {
84 | std::fprintf(stderr, "Failed to initialize library: %d: %s\n", rc, std::strerror(rc));
85 | return 1;
86 | }
87 | NJ_SCOPEGUARD([] { nj::finalize(); });
88 |
89 | nj::Decoder decoder;
90 | if (auto rc = decoder.initialize(); rc) {
91 | std::fprintf(stderr, "Failed to initialize decoder: %#x\n", rc);
92 | return rc;
93 | }
94 | NJ_SCOPEGUARD([&decoder] { decoder.finalize(); });
95 |
96 | nj::Image image(argv[1]);
97 | if (!image.is_valid() || image.parse()) {
98 | std::perror("Invalid file");
99 | return 1;
100 | }
101 |
102 | std::printf("Image dimensions: %ux%u\n", image.width, image.height);
103 |
104 | nj::Surface surf(image.width, image.height, nj::PixelFormat::RGBA);
105 | if (auto rc = surf.allocate(); rc) {
106 | std::fprintf(stderr, "Failed to allocate surface: %#x\n", rc);
107 | return 1;
108 | }
109 |
110 | std::printf("Surface pitch: %#lx, size %#lx\n", surf.pitch, surf.size());
111 |
112 | auto start = std::chrono::system_clock::now();
113 | if (auto rc = decoder.render(image, surf, 255); rc)
114 | std::fprintf(stderr, "Failed to render image: %#x (%s)\n", rc, std::strerror(errno));
115 |
116 | std::size_t read = 0;
117 | decoder.wait(surf, &read);
118 | auto time = std::chrono::system_clock::now() - start;
119 | std::printf("Rendered in %ldµs, read bytes: %lu\n",
120 | std::chrono::duration_cast(time).count(), read);
121 |
122 | display_image(surf);
123 |
124 | return 0;
125 | }
126 |
--------------------------------------------------------------------------------
/examples/bitmap.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | namespace nj {
29 |
30 | class Bmp {
31 | public:
32 | enum class CompressionMethod: std::uint32_t {
33 | Rgb = 0,
34 | Rle8 = 1,
35 | Rle4 = 2,
36 | Bitfields = 3,
37 | Jpeg = 4,
38 | Png = 5,
39 | AlphaBitfields = 6,
40 | Cmyk = 11,
41 | CmykRle8 = 12,
42 | CmykRle4 = 13,
43 |
44 | };
45 |
46 | struct FileHeader {
47 | char magic[2] = { 'B', 'M' };
48 | std::uint32_t size;
49 | std::uint16_t reserved_1 = 0;
50 | std::uint16_t reserved_2 = 0;
51 | std::uint32_t offset = sizeof(FileHeader) + sizeof(ImageHeader);
52 | } __attribute__((packed));
53 |
54 | struct ImageHeader {
55 | std::uint32_t hdr_size = sizeof(ImageHeader);
56 | std::int32_t width;
57 | std::int32_t height;
58 | std::uint16_t num_planes = 1;
59 | std::uint16_t depth = 24;
60 | CompressionMethod compression = CompressionMethod::Rgb;
61 | std::uint32_t size = 0;
62 | std::int32_t resolution_horiz = 0;
63 | std::int32_t resolution_vert = 0;
64 | std::uint32_t num_comps = 0;
65 | std::uint32_t num_comps_2 = 0;
66 | };
67 |
68 | public:
69 | FileHeader file_header;
70 | ImageHeader image_header;
71 |
72 | public:
73 | Bmp(int width, int height, std::uint32_t num_comps = 3) {
74 | this->image_header.width = width;
75 | this->image_header.height = -height; // Negative height -> top to bottom pixel data layout
76 | this->image_header.num_comps = num_comps;
77 | this->file_header.size = sizeof(FileHeader) + sizeof(ImageHeader) + width * height * num_comps;
78 | }
79 |
80 | int write(const nj::Surface &surf, std::string_view path) {
81 | if (surf.type != nj::PixelFormat::BGRA) // Pixel data is in BGR order
82 | return -1;
83 |
84 | auto *fp = std::fopen(path.data(), "wb");
85 | if (!fp)
86 | return 1;
87 |
88 | if (auto ret = std::fwrite(&this->file_header, 1, sizeof(FileHeader), fp); ret != sizeof(FileHeader))
89 | return 2;
90 |
91 | if (auto ret = std::fwrite(&this->image_header, 1, sizeof(ImageHeader), fp); ret != sizeof(ImageHeader))
92 | return 3;
93 |
94 | auto line_width = nj::align_up(this->image_header.width * this->image_header.num_comps, 4u);
95 | std::vector line_buf(line_width * std::abs(this->image_header.height), 0);
96 |
97 | for (auto i = 0; i < std::abs(this->image_header.height); ++i) {
98 | for (auto j = 0; j < this->image_header.width; ++j) {
99 | auto *src = surf.data() + i * surf.pitch + j * surf.get_bpp();
100 | auto *dst = line_buf.data() + i * line_width + j * this->image_header.num_comps;
101 | std::copy_n(src, 3, dst);
102 | }
103 | }
104 |
105 | if (auto ret = std::fwrite(line_buf.data(), 1, line_buf.size(), fp); ret != line_buf.size())
106 | return 4;
107 |
108 | return 0;
109 | }
110 | };
111 |
112 | } // namespace nj
113 |
--------------------------------------------------------------------------------
/include/nvjpg/nv/registers.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 |
23 | namespace nj {
24 |
25 | struct NvjpgRegisters {
26 | std::array reserved_x0;
27 | std::uint32_t operation_type; // 1: decode, 2: encode
28 | std::array reserved_x204;
29 | std::uint32_t execute;
30 | std::array reserved_x304; // nvdec registers?
31 | std::uint32_t control_params; // Bitflags of various debugging options
32 | std::uint32_t picture_index;
33 | std::uint32_t picture_info_offset;
34 | std::uint32_t read_info_offset;
35 | std::uint32_t scan_data_offset;
36 | std::uint32_t out_data_offset;
37 | std::uint32_t out_data_2_offset;
38 | std::uint32_t out_data_3_offset;
39 | };
40 |
41 | struct ThiRegisters {
42 | std::uint32_t incr_syncpt;
43 | std::uint32_t reserved_x4;
44 | std::uint32_t incr_syncpt_err;
45 | std::uint32_t ctxsw_incr_syncpt;
46 | std::array reserved_x10;
47 | std::uint32_t ctxsw;
48 | std::uint32_t reserved_x24;
49 | std::uint32_t cont_syncpt_eof;
50 | std::array reserved_x2c;
51 | std::uint32_t method_0;
52 | std::uint32_t method_1;
53 | std::array reserved_x48;
54 | std::uint32_t int_status;
55 | std::uint32_t int_mask;
56 | };
57 |
58 | #define NJ_REGPOS(regs, member) (offsetof(regs, member) / sizeof(std::uint32_t))
59 |
60 | struct NvjpgPictureInfo {
61 | struct alignas(std::uint32_t) Component {
62 | std::uint8_t sampling_horiz, sampling_vert;
63 | std::uint8_t quant_table_id;
64 | std::uint8_t hm_ac_table_id, hm_dc_table_id;
65 | };
66 |
67 | struct alignas(std::uint32_t) HuffmanTable {
68 | std::array codes;
69 | std::array reserved; // Zero
70 | std::array symbols;
71 | };
72 |
73 | struct alignas(std::uint32_t) QuantizationTable {
74 | std::array table;
75 | };
76 |
77 | std::array hm_ac_tables;
78 | std::array hm_dc_tables;
79 | std::array components;
80 | std::array quant_tables;
81 | std::uint32_t restart_interval;
82 | std::uint32_t width, height;
83 | std::uint32_t num_mcu_h, num_mcu_v;
84 | std::uint32_t num_components;
85 | std::uint32_t scan_data_offset;
86 | std::uint32_t scan_data_size;
87 | std::uint32_t scan_data_samp_layout;
88 | std::uint32_t out_data_samp_layout;
89 | std::uint32_t out_surf_type;
90 | std::uint32_t out_luma_surf_pitch;
91 | std::uint32_t out_chroma_surf_pitch;
92 | std::uint32_t alpha;
93 | std::array yuv2rgb_kernel; // Y gain, VR, UG, VG, UB, Y offset
94 | std::uint32_t tile_mode; // 0: pitch linear, 1, block linear
95 | std::uint32_t gob_height; // If tile mode is block linear
96 | std::uint32_t memory_mode;
97 | std::uint32_t downscale_log_2;
98 | std::array reserved_xb1c;
99 | };
100 | static_assert(sizeof(NvjpgPictureInfo) == 0xb2c);
101 |
102 | struct NvjpgStatus {
103 | std::uint32_t used_bytes;
104 | std::uint32_t mcu_x;
105 | std::uint32_t mcu_y;
106 | std::uint32_t reserved_xc;
107 | std::uint32_t result;
108 | std::uint32_t reserved_x14[3];
109 | };
110 | static_assert(sizeof(NvjpgStatus) == 0x20);
111 |
112 | } // namespace nj
113 |
--------------------------------------------------------------------------------
/examples/render-icons.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | namespace {
25 |
26 | PadState g_pad;
27 |
28 | constexpr std::size_t fb_width = 256 * 16 / 9, fb_height = 256; // Preserve aspect ratio
29 | Framebuffer g_fb;
30 |
31 | }
32 |
33 | extern "C" void userAppInit() {
34 | socketInitializeDefault();
35 | nxlinkStdio();
36 | nsInitialize();
37 | }
38 |
39 | extern "C" void userAppExit() {
40 | nsExit();
41 | socketExit();
42 | }
43 |
44 | static void display_image(const nj::Surface &surface) {
45 | auto *framebuf = static_cast(framebufferBegin(&g_fb, nullptr));
46 | for (std::size_t i = 0; i < std::min(fb_height, surface.height); ++i)
47 | std::copy_n(surface.data() + i * surface.pitch, std::min(surface.width, fb_width) * surface.get_bpp(), framebuf + i * g_fb.stride);
48 | framebufferEnd(&g_fb);
49 |
50 | while (appletMainLoop()) {
51 | padUpdate(&g_pad);
52 | if (padGetButtonsDown(&g_pad) & (HidNpadButton_A | HidNpadButton_Plus))
53 | break;
54 | }
55 | }
56 |
57 | int main(int argc, char **argv) {
58 | if (auto rc = nj::initialize(); rc) {
59 | std::fprintf(stderr, "Failed to initialize library: %d: %s\n", rc, std::strerror(rc));
60 | return 1;
61 | }
62 | NJ_SCOPEGUARD([] { nj::finalize(); });
63 |
64 | nj::Decoder decoder;
65 | if (auto rc = decoder.initialize(); rc) {
66 | std::fprintf(stderr, "Failed to initialize decoder: %#x\n", rc);
67 | return rc;
68 | }
69 | NJ_SCOPEGUARD([&decoder] { decoder.finalize(); });
70 |
71 | std::printf("Press A to show the next icon, + to exit\n");
72 |
73 | padConfigureInput(1, HidNpadStyleSet_NpadStandard);
74 | padInitializeDefault(&g_pad);
75 |
76 | framebufferCreate(&g_fb, nwindowGetDefault(), fb_width, fb_height, PIXEL_FORMAT_RGBA_8888, 2);
77 | framebufferMakeLinear(&g_fb);
78 |
79 | framebufferBegin(&g_fb, nullptr);
80 | framebufferEnd(&g_fb);
81 |
82 | int title_offset = 0;
83 | NsApplicationRecord record;
84 | auto control_data = std::make_shared>(sizeof(NsApplicationControlData));
85 | while (appletMainLoop()) {
86 | s32 count;
87 | if (R_FAILED(nsListApplicationRecord(&record, 1, title_offset, &count)) || !record.application_id) {
88 | title_offset = 0;
89 | continue;
90 | }
91 |
92 | title_offset++;
93 |
94 | std::printf("Showing icon for %#018lx\n", record.application_id);
95 |
96 | NJ_TRY_RET(nsGetApplicationControlData(NsApplicationControlSource_Storage, record.application_id,
97 | reinterpret_cast(control_data->data()), control_data->size(), nullptr));
98 |
99 | // We avoid copying the icon data by directly passing the whole control structure
100 | // The JFIF parser will skip the nacp data while looking for the SOI tag
101 | nj::Image image(control_data);
102 | if (!image.is_valid() || image.parse()) {
103 | std::perror("Invalid file");
104 | return 1;
105 | }
106 |
107 | nj::Surface surf(image.width, image.height, nj::PixelFormat::RGBA);
108 | if (auto rc = surf.allocate(); rc) {
109 | std::fprintf(stderr, "Failed to allocate surface: %#x\n", rc);
110 | return 1;
111 | }
112 |
113 | auto start = std::chrono::system_clock::now();
114 | if (auto rc = decoder.render(image, surf, 255); rc)
115 | std::fprintf(stderr, "Failed to render image: %#x (%s)\n", rc, std::strerror(errno));
116 |
117 | std::size_t read = 0;
118 | decoder.wait(surf, &read);
119 | auto time = std::chrono::system_clock::now() - start;
120 | std::printf("Rendered in %ldµs, read bytes: %lu\n",
121 | std::chrono::duration_cast(time).count(), read);
122 |
123 | display_image(surf);
124 |
125 | if (padGetButtonsDown(&g_pad) & HidNpadButton_Plus)
126 | break;
127 | }
128 |
129 | framebufferClose(&g_fb);
130 |
131 | return 0;
132 | }
133 |
--------------------------------------------------------------------------------
/include/nvjpg/nv/ioctl_types.h:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | #include
25 |
26 | #ifdef __SWITCH__
27 | # include
28 | #else
29 | # include
30 | #endif
31 |
32 | #ifdef __SWITCH__
33 |
34 | typedef nvioctl_fence nvhost_ctrl_fence;
35 | typedef nvioctl_cmdbuf nvhost_cmdbuf;
36 | typedef nvioctl_syncpt_incr nvhost_syncpt_incr;
37 |
38 | #else
39 |
40 | typedef struct {
41 | uint32_t size;
42 | uint32_t handle;
43 | } nvmap_create_args;
44 |
45 | typedef struct {
46 | uint32_t handle;
47 | uint32_t heap_mask;
48 | uint32_t flags;
49 | uint32_t align;
50 | } nvmap_alloc_args;
51 |
52 | typedef struct {
53 | uint64_t addr;
54 | uint32_t handle;
55 | uint32_t len;
56 | int32_t op;
57 | } nvmap_cache_args;
58 |
59 | #define NVMAP_IOCTL_MAGIC 'N'
60 | #define NVMAP_IOCTL_CREATE _IOWR(NVMAP_IOCTL_MAGIC, 0, nvmap_create_args)
61 | #define NVMAP_IOCTL_ALLOC _IOW (NVMAP_IOCTL_MAGIC, 3, nvmap_alloc_args)
62 | #define NVMAP_IOCTL_FREE _IO (NVMAP_IOCTL_MAGIC, 4)
63 | #define NVMAP_IOCTL_CACHE _IOW (NVMAP_IOCTL_MAGIC, 12, nvmap_cache_args)
64 |
65 | typedef struct {
66 | uint32_t id;
67 | uint32_t value;
68 | } nvhost_ctrl_fence;
69 |
70 | typedef struct {
71 | uint32_t id;
72 | uint32_t thresh;
73 | int32_t timeout;
74 | uint32_t value;
75 | } nvhost_ctrl_syncpt_waitex_args;
76 |
77 | #define NVHOST_IOCTL_MAGIC 'H'
78 | #define NVHOST_IOCTL_CTRL_SYNCPT_WAITEX _IOWR(NVHOST_IOCTL_MAGIC, 6, nvhost_ctrl_syncpt_waitex_args)
79 |
80 | typedef struct {
81 | uint32_t rate;
82 | uint32_t moduleid;
83 | } nvhost_clk_rate_args;
84 |
85 | typedef struct {
86 | uint32_t param;
87 | uint32_t value;
88 | } nvhost_get_param_args;
89 |
90 | typedef struct {
91 | uint32_t mem;
92 | uint32_t offset;
93 | uint32_t words;
94 | } nvhost_cmdbuf;
95 |
96 | typedef struct {
97 | int32_t pre_fence;
98 | uint32_t reserved;
99 | } nvhost_cmdbuf_ext;
100 |
101 | typedef struct {
102 | uint32_t cmdbuf_mem;
103 | uint32_t cmdbuf_offset;
104 | uint32_t target_mem;
105 | uint32_t target_offset;
106 | } nvhost_reloc;
107 |
108 | typedef struct {
109 | uint32_t shift;
110 | } nvhost_reloc_shift;
111 |
112 | typedef struct {
113 | uint32_t reloc_type;
114 | uint32_t padding;
115 | } nvhost_reloc_type;
116 |
117 | typedef struct {
118 | uint32_t mem;
119 | uint32_t offset;
120 | uint32_t syncpt_id;
121 | uint32_t thresh;
122 | } nvhost_waitchk;
123 |
124 | typedef struct {
125 | uint32_t syncpt_id;
126 | uint32_t syncpt_incrs;
127 | } nvhost_syncpt_incr;
128 |
129 | typedef struct {
130 | uint32_t submit_version;
131 | uint32_t num_syncpt_incrs;
132 | uint32_t num_cmdbufs;
133 | uint32_t num_relocs;
134 | uint32_t num_waitchks;
135 | uint32_t timeout;
136 | uint32_t flags;
137 | uint32_t fence;
138 | uintptr_t syncpt_incrs;
139 | uintptr_t cmdbuf_exts;
140 |
141 | uint32_t checksum_methods;
142 | uint32_t checksum_falcon_methods;
143 |
144 | uint64_t pad[1];
145 |
146 | uintptr_t reloc_types;
147 | uintptr_t cmdbufs;
148 | uintptr_t relocs;
149 | uintptr_t reloc_shifts;
150 | uintptr_t waitchks;
151 | uintptr_t waitbases;
152 | uintptr_t class_ids;
153 | uintptr_t fences;
154 | } nvhost_submit_args;
155 |
156 | #define NVHOST_SUBMIT_VERSION_V2 2
157 |
158 | #define NVHOST_IOCTL_CHANNEL_GET_CLK_RATE _IOWR(NVHOST_IOCTL_MAGIC, 9, nvhost_clk_rate_args)
159 | #define NVHOST_IOCTL_CHANNEL_SET_CLK_RATE _IOW (NVHOST_IOCTL_MAGIC, 10, nvhost_clk_rate_args)
160 | #define NVHOST_IOCTL_CHANNEL_GET_SYNCPOINT _IOWR(NVHOST_IOCTL_MAGIC, 16, nvhost_get_param_args)
161 | #define NVHOST_IOCTL_CHANNEL_SUBMIT _IOWR(NVHOST_IOCTL_MAGIC, 26, nvhost_submit_args)
162 |
163 | #endif
164 |
165 | enum {
166 | NVMAP_CACHE_OP_WB = 0,
167 | NVMAP_CACHE_OP_INV = 1,
168 | NVMAP_CACHE_OP_WB_INV = 2,
169 | };
170 |
171 | enum {
172 | NVHOST_RELOC_TYPE_DEFAULT = 0,
173 | NVHOST_RELOC_TYPE_PITCH_LINEAR = 1,
174 | NVHOST_RELOC_TYPE_BLOCK_LINEAR = 2,
175 | NVHOST_RELOC_TYPE_NVLINK = 3,
176 | };
177 |
178 | #ifdef __cplusplus
179 | }
180 | #endif // extern "C"
181 |
--------------------------------------------------------------------------------
/examples/render-yuv.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | static void display_image(const nj::VideoSurface &surface) {
25 | if (SDL_Init(SDL_INIT_VIDEO) < 0) {
26 | std::fprintf(stderr, "Could not initialize sdl2: %s\n", SDL_GetError());
27 | return;
28 | }
29 | NJ_SCOPEGUARD([] { SDL_Quit(); });
30 |
31 | auto *window = SDL_CreateWindow("nvjpg",
32 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
33 | 1080, 720, SDL_WINDOW_SHOWN);
34 | if (!window) {
35 | std::fprintf(stderr, "Could not create window: %s\n", SDL_GetError());
36 | return;
37 | }
38 | NJ_SCOPEGUARD([&window] { SDL_DestroyWindow(window); });
39 |
40 | auto *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
41 | if (!renderer) {
42 | std::fprintf(stderr, "Could not create renderer: %s\n", SDL_GetError());
43 | return;
44 | }
45 | NJ_SCOPEGUARD([&renderer] { SDL_DestroyRenderer(renderer); });
46 |
47 | auto *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, surface.width, surface.height);
48 | if (!texture) {
49 | std::fprintf(stderr, "Could not create texture: %s\n", SDL_GetError());
50 | return;
51 | }
52 | NJ_SCOPEGUARD([&texture] { SDL_DestroyTexture(texture); });
53 |
54 | if (auto rc = SDL_UpdateYUVTexture(texture, nullptr, surface.luma_data, surface.luma_pitch,
55 | surface.chromab_data, surface.chroma_pitch, surface.chromar_data, surface.chroma_pitch); rc) {
56 | std::fprintf(stderr, "Failed to update texture: %s\n", SDL_GetError());
57 | return;
58 | }
59 |
60 | int width, height;
61 | SDL_GetWindowSize(window, &width, &height);
62 |
63 | SDL_Rect rect = {
64 | .x = 0, .y = 0,
65 | .w = std::min(static_cast(surface.width), width),
66 | .h = std::min(static_cast(surface.height), height),
67 | };
68 |
69 | if (auto rc = SDL_RenderCopy(renderer, texture, &rect, &rect); rc) {
70 | std::fprintf(stderr, "Failed to render texture: %s\n", SDL_GetError());
71 | return;
72 | }
73 |
74 | SDL_RenderPresent(renderer);
75 |
76 | SDL_Event e;
77 | while (true) {
78 | if (!SDL_WaitEvent(&e)) {
79 | std::fprintf(stderr, "Error while waiting on an event: %s\n", SDL_GetError());
80 | return;
81 | }
82 |
83 | if (e.type == SDL_QUIT)
84 | break;
85 | }
86 | }
87 |
88 | int main(int argc, char **argv) {
89 | if (argc < 2) {
90 | std::fprintf(stderr, "Usage: %s jpg\n", argv[0]);
91 | return 1;
92 | }
93 |
94 | if (auto rc = nj::initialize(); rc) {
95 | std::fprintf(stderr, "Failed to initialize library: %d: %s\n", rc, std::strerror(rc));
96 | return 1;
97 | }
98 | NJ_SCOPEGUARD([] { nj::finalize(); });
99 |
100 | nj::Decoder decoder;
101 | if (auto rc = decoder.initialize(); rc) {
102 | std::fprintf(stderr, "Failed to initialize decoder: %#x\n", rc);
103 | return rc;
104 | }
105 | NJ_SCOPEGUARD([&decoder] { decoder.finalize(); });
106 |
107 | nj::Image image(argv[1]);
108 | if (!image.is_valid() || image.parse()) {
109 | std::perror("Invalid file");
110 | return 1;
111 | }
112 |
113 | std::printf("Image dimensions: %ux%u\n", image.width, image.height);
114 |
115 | // Note: SDL only support YV12 so setting the color format to something else will offset the chroma data in the display window
116 | nj::VideoSurface surf(image.width, image.height, nj::SamplingFormat::S420);
117 | if (auto rc = surf.allocate(); rc) {
118 | std::fprintf(stderr, "Failed to allocate surface: %#x\n", rc);
119 | return 1;
120 | }
121 |
122 | std::printf("Surface luma pitch: %#lx, chroma pitch: %#lx, size %#lx\n", surf.luma_pitch, surf.chroma_pitch, surf.size());
123 |
124 | auto start = std::chrono::system_clock::now();
125 | if (auto rc = decoder.render(image, surf); rc)
126 | std::fprintf(stderr, "Failed to render image: %#x (%s)\n", rc, std::strerror(errno));
127 |
128 | std::size_t read = 0;
129 | decoder.wait(surf, &read);
130 | auto time = std::chrono::system_clock::now() - start;
131 | std::printf("Rendered in %ldµs, read bytes: %lu\n",
132 | std::chrono::duration_cast(time).count(), read);
133 |
134 | display_image(surf);
135 |
136 | return 0;
137 | }
138 |
--------------------------------------------------------------------------------
/include/nvjpg/nv/cmdbuf.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 | #include
23 |
24 | #include
25 | #include
26 | #include
27 |
28 | namespace nj {
29 |
30 | struct Host1xOpcode {
31 | std::uint32_t value = 0;
32 |
33 | constexpr inline operator std::uint32_t() const {
34 | return this->value;
35 | }
36 | };
37 |
38 | struct OpcodeSetClass: public Host1xOpcode {
39 | constexpr OpcodeSetClass(std::uint32_t class_id, std::uint32_t offset = 0, std::uint32_t mask = 0) {
40 | this->value = (0 << 28) | (offset << 16) | (class_id << 6) | mask;
41 | }
42 | };
43 |
44 | struct OpcodeIncr: public Host1xOpcode {
45 | constexpr OpcodeIncr(std::uint32_t offset, std::uint32_t count) {
46 | this->value = (1 << 28) | (offset << 16) | count;
47 | }
48 | };
49 |
50 | struct OpcodeNonIncr: public Host1xOpcode {
51 | constexpr OpcodeNonIncr(std::uint32_t offset, std::uint32_t count) {
52 | this->value = (2 << 28) | (offset << 16) | count;
53 | }
54 | };
55 |
56 | struct OpcodeMask: public Host1xOpcode {
57 | constexpr OpcodeMask(std::uint32_t offset, std::uint32_t mask) {
58 | this->value = (3 << 28) | (offset << 16) | mask;
59 | }
60 | };
61 |
62 | struct OpcodeImm: public Host1xOpcode {
63 | constexpr OpcodeImm(std::uint32_t offset, std::uint32_t value) {
64 | this->value = (4 << 28) | (offset << 16) | value;
65 | }
66 | };
67 |
68 | class CmdBuf {
69 | public:
70 | using Word = std::uint32_t;
71 |
72 | public:
73 | CmdBuf(const NvMap &map): map(map), cur_word(static_cast(map.address())) { }
74 |
75 | std::size_t size() const {
76 | return static_cast(this->cur_word - static_cast(this->map.address()));
77 | }
78 |
79 | void begin(std::uint32_t class_id, std::int32_t pre_fence = -1) {
80 | this->bufs.push_back({ this->map.handle(), static_cast(this->size() * sizeof(Word)), 0 });
81 |
82 | this->cur_buf_begin = this->size();
83 |
84 | #ifndef __SWITCH__
85 | this->exts.push_back({ pre_fence, 0 });
86 | this->class_ids.push_back(class_id);
87 | #endif
88 | }
89 |
90 | void end() {
91 | this->bufs.back().words = this->size() - this->cur_buf_begin;
92 | }
93 |
94 | void clear() {
95 | this->cur_word = static_cast(this->map.address());
96 | this->bufs .clear();
97 | #ifndef __SWITCH__
98 | this->exts .clear(), this->class_ids.clear();
99 | this->relocs.clear(), this->shifts .clear(), this->types.clear();
100 | #endif
101 | }
102 |
103 | void push_raw(Word word) {
104 | *this->cur_word++ = word;
105 | }
106 |
107 | void push_value(std::uint32_t offset, std::uint32_t value) {
108 | constexpr auto op = OpcodeIncr(NJ_REGPOS(ThiRegisters, method_0), 2);
109 |
110 | this->push_raw(op);
111 | this->push_raw(offset);
112 | this->push_raw(value);
113 | }
114 |
115 | void push_reloc(std::uint32_t offset, const NvMap &target, std::uint32_t target_offset = 0,
116 | std::uint32_t shift = 8, std::uint32_t type = NVHOST_RELOC_TYPE_DEFAULT) {
117 | #ifdef __SWITCH__
118 | // On the Switch, resolve relocations on the client side
119 | this->push_value(offset, (target.iova() + target_offset) >> shift);
120 | #else
121 | this->push_value(offset, 0xdeadbeef); // Officially used placeholder value
122 |
123 | this->relocs.push_back({
124 | .cmdbuf_mem = this->map.handle(),
125 | .cmdbuf_offset = static_cast((this->size() - 1) * sizeof(Word)),
126 | .target_mem = target.handle(),
127 | .target_offset = target_offset,
128 | });
129 | this->shifts.push_back({ shift });
130 | this->types.push_back({ type, 0 });
131 | #endif
132 | }
133 |
134 | #ifdef __SWITCH__
135 | auto &get_bufs() {
136 | return this->bufs;
137 | }
138 | #else
139 | auto get_bufs() const {
140 | return std::make_tuple(this->bufs, this->exts, this->class_ids);
141 | }
142 |
143 | auto get_relocs() const {
144 | return std::make_tuple(this->relocs, this->shifts, this->types);
145 | }
146 | #endif
147 |
148 | private:
149 | const NvMap ↦
150 | Word *cur_word;
151 |
152 | std::size_t cur_buf_begin = 0;
153 |
154 | std::vector bufs;
155 |
156 | #ifndef __SWITCH__
157 | std::vector exts;
158 | std::vector class_ids;
159 |
160 | std::vector relocs;
161 | std::vector shifts;
162 | std::vector types;
163 | #endif
164 | };
165 |
166 | } // namespace nj
167 |
--------------------------------------------------------------------------------
/include/nvjpg/surface.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 | #include
23 |
24 | #include
25 | #include
26 |
27 | #if defined(__SWITCH__) && __has_include()
28 | # include
29 | #endif
30 |
31 | namespace nj {
32 |
33 | enum class PixelFormat {
34 | YUV = 0,
35 | RGB = 1,
36 | BGR = 2,
37 | RGBA = 3,
38 | BGRA = 4,
39 | ABGR = 5,
40 | ARGB = 6,
41 | };
42 |
43 | enum class SamplingFormat {
44 | Monochrome = 0,
45 | S420 = 1,
46 | S422 = 2,
47 | S440 = 3,
48 | S444 = 4,
49 | };
50 |
51 | enum class MemoryMode {
52 | SemiPlanarNv12 = 0,
53 | SemiPlanarNv21 = 1,
54 | SinglyPlanar = 2,
55 | Planar = 3,
56 | };
57 |
58 | class SurfaceBase {
59 | public:
60 | std::size_t width, height;
61 | PixelFormat type;
62 |
63 | public:
64 | constexpr SurfaceBase(std::size_t width, std::size_t height, PixelFormat type):
65 | width(width), height(height), type(type) { }
66 |
67 | const std::uint8_t *data() const {
68 | return static_cast(this->map.address());
69 | }
70 |
71 | std::size_t size() const {
72 | return this->map.size();
73 | }
74 |
75 | const NvMap &get_map() const {
76 | return this->map;
77 | }
78 |
79 | protected:
80 | NvMap map;
81 |
82 | #ifdef __SWITCH__
83 | NvFence render_fence = {};
84 | #else
85 | nvhost_ctrl_fence render_fence = {};
86 | #endif
87 |
88 | friend class Decoder;
89 | };
90 |
91 | class Surface: public SurfaceBase {
92 | public:
93 | std::size_t pitch;
94 |
95 | public:
96 | constexpr Surface(std::size_t width, std::size_t height, PixelFormat pixel_fmt = PixelFormat::RGBA):
97 | SurfaceBase(width, height, pixel_fmt) { }
98 |
99 | int allocate();
100 |
101 | constexpr int get_bpp() const {
102 | switch (this->type) {
103 | case PixelFormat::RGB ... PixelFormat::BGR:
104 | return 3;
105 | case PixelFormat::RGBA ... PixelFormat::ARGB:
106 | default:
107 | return 4;
108 | }
109 | }
110 |
111 | #if defined(__SWITCH__) && __has_include()
112 | std::tuple to_deko3d(dk::Device device, std::uint32_t flags = 0) const {
113 | auto map_dk_fmt = [](PixelFormat fmt) {
114 | switch (fmt) {
115 | case PixelFormat::RGB:
116 | return DkImageFormat_RGBX8_Unorm;
117 | case PixelFormat::BGR:
118 | return DkImageFormat_BGRX8_Unorm;
119 | case PixelFormat::RGBA:
120 | default:
121 | return DkImageFormat_RGBA8_Unorm;
122 | case PixelFormat::BGRA:
123 | return DkImageFormat_BGRA8_Unorm;
124 | }
125 | };
126 |
127 | dk::ImageLayout layout;
128 | dk::ImageLayoutMaker{device}
129 | .setFlags(flags | DkImageFlags_PitchLinear)
130 | .setPitchStride(this->pitch)
131 | .setFormat(map_dk_fmt(this->type))
132 | .setDimensions(this->width, this->height)
133 | .initialize(layout);
134 |
135 | auto image_size = align_up(static_cast(layout.getSize()), layout.getAlignment());
136 | auto image_memblock = dk::MemBlockMaker(device, image_size)
137 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image)
138 | .setStorage(this->map.address())
139 | .create();
140 |
141 | dk::Image image;
142 | image.initialize(layout, image_memblock, 0);
143 |
144 | return { image_memblock, image };
145 | }
146 | #endif
147 |
148 | constexpr auto get_memory_mode() const {
149 | return MemoryMode::Planar;
150 | }
151 | };
152 |
153 | class VideoSurface: public SurfaceBase {
154 | public:
155 | SamplingFormat sampling;
156 | std::size_t luma_pitch, chroma_pitch;
157 | const std::uint8_t *luma_data, *chromab_data, *chromar_data;
158 |
159 | public:
160 | constexpr VideoSurface(std::size_t width, std::size_t height, SamplingFormat sampling = SamplingFormat::S420):
161 | SurfaceBase(width, height, PixelFormat::YUV), sampling(sampling) { }
162 |
163 | int allocate();
164 |
165 | constexpr int get_depth() const {
166 | switch (this->sampling) {
167 | case SamplingFormat::S420:
168 | default:
169 | return 12;
170 | case SamplingFormat::S422:
171 | return 16;
172 | case SamplingFormat::S440:
173 | return 16;
174 | case SamplingFormat::S444:
175 | return 24;
176 | }
177 | }
178 |
179 | constexpr auto get_memory_mode() const {
180 | return MemoryMode::Planar;
181 | }
182 | };
183 |
184 | } // namespace nj
185 |
--------------------------------------------------------------------------------
/include/nvjpg/nv/channel.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #include
19 | #include
20 |
21 | #ifdef __SWITCH__
22 | # include
23 | #else
24 | # include
25 | # include
26 | # include
27 | # include
28 | # include
29 | #endif
30 |
31 | #include
32 | #include
33 |
34 | namespace nj {
35 |
36 | class NvChannel {
37 | public:
38 | constexpr NvChannel() = default;
39 |
40 | ~NvChannel() {
41 | #ifdef __SWITCH__
42 | if (this->channel.fd != -1u)
43 | #else
44 | if (this->fd != -1)
45 | #endif
46 | this->close();
47 | }
48 |
49 | Result open(const char *path) {
50 | #ifdef __SWITCH__
51 | NJ_TRY_RET(nvChannelCreate(&this->channel, path));
52 | NJ_TRY_RET(nvioctlChannel_GetSyncpt(this->channel.fd, 0, &this->syncpt));
53 | return 0;
54 | #else
55 | this->fd = ::open(path, O_RDWR | O_CLOEXEC);
56 | if (this->fd < 0)
57 | return this->fd;
58 |
59 | nvhost_get_param_args args = {
60 | .param = 0,
61 | .value = -1u,
62 | };
63 |
64 | auto rc = ::ioctl(this->fd, NVHOST_IOCTL_CHANNEL_GET_SYNCPOINT, &args);
65 | if (!rc)
66 | this->syncpt = args.value;
67 |
68 | return this->fd;
69 | #endif
70 | }
71 |
72 | Result close() {
73 | #ifdef __SWITCH__
74 | nvChannelClose(&this->channel);
75 | return 0;
76 | #else
77 | auto rc = ::close(this->fd);
78 | if (rc != -1)
79 | this->fd = -1;
80 | return rc;
81 | #endif
82 | }
83 |
84 | Result get_clock_rate(std::uint32_t id, std::uint32_t &rate) const {
85 | #ifdef __SWITCH__
86 | return 0;
87 | #else
88 | nvhost_clk_rate_args args = {
89 | .rate = 0,
90 | .moduleid = id,
91 | };
92 |
93 | auto rc = ::ioctl(this->fd, NVHOST_IOCTL_CHANNEL_GET_CLK_RATE, &args);
94 | if (!rc)
95 | rate = args.rate;
96 |
97 | return rc;
98 | #endif
99 | }
100 |
101 | Result set_clock_rate(std::uint32_t id, std::uint32_t rate) const {
102 | #ifdef __SWITCH__
103 | return 0;
104 | #else
105 | nvhost_clk_rate_args args = {
106 | .rate = rate,
107 | .moduleid = id,
108 | };
109 |
110 | return ::ioctl(this->fd, NVHOST_IOCTL_CHANNEL_SET_CLK_RATE, &args);
111 | #endif
112 | }
113 |
114 | #ifdef __SWITCH__
115 | Result submit(std::span cmdbufs, std::span relocs, std::span shifts,
116 | std::span incrs, std::span fences) {
117 | return nvioctlChannel_Submit(this->channel.fd, cmdbufs.data(), static_cast(cmdbufs.size()),
118 | relocs.data(), shifts.data(), static_cast(relocs.size()),
119 | incrs.data(), static_cast(incrs.size()),
120 | fences.data(), static_cast(fences.size()));
121 | }
122 | #else
123 | Result submit(std::span cmdbufs, std::span exts, std::span class_ids,
124 | std::span relocs, std::span shifts, std::span types,
125 | std::span incrs, std::span fences, nvhost_ctrl_fence &fence) const {
126 | nvhost_submit_args args = {
127 | .submit_version = NVHOST_SUBMIT_VERSION_V2,
128 | .num_syncpt_incrs = static_cast(incrs.size()),
129 | .num_cmdbufs = static_cast(cmdbufs.size()),
130 | .num_relocs = static_cast(relocs.size()),
131 | .num_waitchks = 0,
132 | .timeout = 0,
133 | .flags = 0,
134 | .fence = 0,
135 | .syncpt_incrs = reinterpret_cast(incrs.data()),
136 | .cmdbuf_exts = reinterpret_cast(exts.data()),
137 | .checksum_methods = 0,
138 | .checksum_falcon_methods = 0,
139 | .pad = { 0 },
140 | .reloc_types = reinterpret_cast(types.data()),
141 | .cmdbufs = reinterpret_cast(cmdbufs.data()),
142 | .relocs = reinterpret_cast(relocs.data()),
143 | .reloc_shifts = reinterpret_cast(shifts.data()),
144 | .waitchks = reinterpret_cast(nullptr),
145 | .waitbases = reinterpret_cast(nullptr),
146 | .class_ids = reinterpret_cast(class_ids.data()),
147 | .fences = reinterpret_cast(fences.data()),
148 | };
149 |
150 | auto rc = ::ioctl(this->fd, NVHOST_IOCTL_CHANNEL_SUBMIT, &args);
151 | if (!rc)
152 | fence.value = args.fence;
153 |
154 | return rc;
155 | }
156 | #endif
157 |
158 | Result get_fd() const {
159 | #ifdef __SWITCH__
160 | return this->channel.fd;
161 | #else
162 | return this->fd;
163 | #endif
164 | }
165 |
166 | std::uint32_t get_syncpt() const {
167 | return this->syncpt;
168 | }
169 |
170 | private:
171 | std::uint32_t syncpt = 0;
172 |
173 | #ifdef __SWITCH__
174 | ::NvChannel channel = {};
175 | #else
176 | int fd = 0;
177 | #endif
178 | };
179 |
180 | } // namespace nj
181 |
--------------------------------------------------------------------------------
/include/nvjpg/nv/map.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 |
23 | #ifdef __SWITCH__
24 | # include
25 | #else
26 | # include
27 | # include
28 | # include
29 | # include
30 | # include
31 | #endif
32 |
33 | #include
34 | #include
35 |
36 | namespace nj {
37 |
38 | class NvMap {
39 | public:
40 | static Result initialize() {
41 | #ifdef __SWITCH__
42 | return nvMapInit();
43 | #else
44 | return NvMap::nvmap_fd = ::open("/dev/nvmap", O_RDWR | O_SYNC | O_CLOEXEC);
45 | #endif
46 | }
47 |
48 | static Result finalize() {
49 | #ifdef __SWITCH__
50 | nvMapExit();
51 | return 0;
52 | #else
53 | return ::close(NvMap::nvmap_fd);
54 | #endif
55 | }
56 |
57 | constexpr NvMap() = default;
58 |
59 | ~NvMap() {
60 | #ifdef __SWITCH__
61 | if (this->addr)
62 | this->unmap();
63 |
64 | if (this->nvmap.cpu_addr)
65 | this->free();
66 | #else
67 | if (this->addr)
68 | this->unmap();
69 |
70 | if (this->hdl)
71 | this->free();
72 | #endif
73 | }
74 |
75 | Result allocate(std::uint32_t size, std::uint32_t align, std::uint32_t flags) {
76 | #ifdef __SWITCH__
77 | NJ_UNUSED(flags);
78 |
79 | size = align_up(size, 0x1000u), align = align_up(align, 0x1000u);
80 | auto *mapmem = new (std::align_val_t(align)) std::uint8_t[size];
81 | if (!mapmem)
82 | return MAKERESULT(Module_Libnx, LibnxError_OutOfMemory);
83 |
84 | return nvMapCreate(&this->nvmap, mapmem, size, align, NvKind_Pitch, false);
85 | #else
86 | nvmap_create_args create = {
87 | .size = size,
88 | .handle = 0,
89 | };
90 |
91 | auto rc = ::ioctl(NvMap::nvmap_fd, NVMAP_IOCTL_CREATE, &create);
92 | if (rc)
93 | return rc;
94 |
95 | this->sz = size;
96 | this->hdl = create.handle;
97 |
98 | nvmap_alloc_args alloc = {
99 | .handle = this->hdl,
100 | .heap_mask = 0x40000000,
101 | .flags = flags,
102 | .align = align,
103 | };
104 |
105 | return ::ioctl(NvMap::nvmap_fd, NVMAP_IOCTL_ALLOC, &alloc);
106 | #endif
107 | }
108 |
109 | Result free() {
110 | if (this->addr)
111 | NJ_TRY_RET(this->unmap());
112 |
113 | #ifdef __SWITCH__
114 | delete[] static_cast(this->nvmap.cpu_addr);
115 | nvMapClose(&this->nvmap);
116 | return 0;
117 | #else
118 | auto rc = ::ioctl(NvMap::nvmap_fd, NVMAP_IOCTL_FREE, this->hdl);
119 | if (!rc)
120 | this->hdl = 0;
121 |
122 | return rc;
123 | #endif
124 | }
125 |
126 | #ifdef __SWITCH__
127 | Result map(int channel_fd, bool compressed = false) {
128 | nvioctl_command_buffer_map params = { .handle = this->nvmap.handle };
129 | NJ_TRY_RET(nvioctlChannel_MapCommandBuffer(channel_fd, ¶ms, 1, compressed));
130 | this->owner = channel_fd, this->compressed = compressed, this->addr = params.iova;
131 | return 0;
132 | }
133 | #else
134 | void *map(int prot = PROT_READ | PROT_WRITE, int flags = MAP_SHARED) {
135 | return this->addr = ::mmap(nullptr, this->sz, prot, flags, this->hdl, 0);
136 | }
137 | #endif
138 |
139 | Result unmap() {
140 | #ifdef __SWITCH__
141 | nvioctl_command_buffer_map params = { .handle = this->nvmap.handle };
142 | NJ_TRY_RET(nvioctlChannel_UnmapCommandBuffer(this->owner, ¶ms, 1, this->compressed));
143 | this->addr = 0;
144 | return 0;
145 | #else
146 | auto rc = ::munmap(this->addr, sz);
147 | if (!rc)
148 | this->addr = nullptr;
149 |
150 | return rc;
151 | #endif
152 | }
153 |
154 | Result cache(std::size_t size, std::int32_t op = NVMAP_CACHE_OP_WB) const {
155 | #ifdef __SWITCH__
156 | return 0;
157 | #else
158 | nvmap_cache_args args = {
159 | .addr = reinterpret_cast(this->addr),
160 | .handle = this->hdl,
161 | .len = static_cast(size),
162 | .op = op,
163 | };
164 |
165 | return ::ioctl(NvMap::nvmap_fd, NVMAP_IOCTL_CACHE, &args);
166 | #endif
167 | }
168 |
169 | Result cache(std::uint32_t op = NVMAP_CACHE_OP_WB) const {
170 | #ifdef __SWITCH__
171 | return 0;
172 | #else
173 | return this->cache(this->sz, op);
174 | #endif
175 | }
176 |
177 | std::size_t size() const {
178 | #ifdef __SWITCH__
179 | return this->nvmap.size;
180 | #else
181 | return this->sz;
182 | #endif
183 | }
184 |
185 | std::uint32_t handle() const {
186 | #ifdef __SWITCH__
187 | return this->nvmap.handle;
188 | #else
189 | return this->hdl;
190 | #endif
191 | }
192 |
193 | void *address() const {
194 | #ifdef __SWITCH__
195 | return this->nvmap.cpu_addr;
196 | #else
197 | return this->addr;
198 | #endif
199 | }
200 |
201 | #ifdef __SWITCH__
202 | std::uint32_t iova() const {
203 | return this->addr;
204 | }
205 | #endif
206 |
207 | private:
208 | #ifdef __SWITCH__
209 | ::NvMap nvmap = {};
210 | std::uint32_t addr = 0;
211 | std::uint32_t owner = 0;
212 | bool compressed = false;
213 | #else
214 | std::uint32_t sz = 0;
215 | std::uint32_t hdl = 0;
216 | void *addr = nullptr;
217 |
218 | static inline int nvmap_fd;
219 | #endif
220 | };
221 |
222 | } // namespace nj
223 |
--------------------------------------------------------------------------------
/examples/render-deko3d.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | // In this example, we render the decoded data by directly blitting it onto the framebuffer
19 | // For more conventional use of textures within deko3d, see:
20 | // https://github.com/switchbrew/switch-examples/tree/master/graphics/deko3d/deko_examples
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 |
33 | extern "C" void userAppInit() {
34 | socketInitializeDefault();
35 | nxlinkStdio();
36 | }
37 |
38 | extern "C" void userAppExit() {
39 | socketExit();
40 | }
41 |
42 | namespace {
43 |
44 | constexpr std::uint32_t FB_WIDTH = 1280;
45 | constexpr std::uint32_t FB_HEIGHT = 720;
46 | constexpr std::uint32_t FB_NUM = 2;
47 | constexpr std::size_t CMDBUF_SIZE = 10 * DK_MEMBLOCK_ALIGNMENT;
48 |
49 | dk::UniqueDevice device;
50 | dk::UniqueMemBlock fb_memblock, cmdbuf_memblock, image_memblock;
51 | dk::UniqueSwapchain swapchain;
52 | dk::UniqueCmdBuf cmdbuf;
53 | dk::UniqueQueue queue;
54 |
55 | dk::Image image;
56 | std::array framebuffer_images;
57 |
58 | std::array render_cmdlists;
59 | std::array bind_fb_cmdlists;
60 |
61 | } // namespace
62 |
63 | void deko_init() {
64 | device = dk::DeviceMaker().create();
65 |
66 | dk::ImageLayout fb_layout;
67 | dk::ImageLayoutMaker(device)
68 | .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression | DkImageFlags_Usage2DEngine)
69 | .setFormat(DkImageFormat_RGBA8_Unorm)
70 | .setDimensions(FB_WIDTH, FB_HEIGHT)
71 | .initialize(fb_layout);
72 |
73 | auto fb_size = nj::align_up(static_cast(fb_layout.getSize()), fb_layout.getAlignment());
74 | fb_memblock = dk::MemBlockMaker(device, FB_NUM * fb_size)
75 | .setFlags(DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image)
76 | .create();
77 |
78 | std::array swapchain_images = {
79 | &framebuffer_images[0], &framebuffer_images[1],
80 | };
81 |
82 | for (std::size_t i = 0; i < framebuffer_images.size(); ++i)
83 | framebuffer_images[i].initialize(fb_layout, fb_memblock, i * fb_size);
84 |
85 | swapchain = dk::SwapchainMaker(device, nwindowGetDefault(), swapchain_images)
86 | .create();
87 |
88 | queue = dk::QueueMaker(device)
89 | .setFlags(DkQueueFlags_Graphics)
90 | .create();
91 |
92 | cmdbuf_memblock = dk::MemBlockMaker(device, CMDBUF_SIZE)
93 | .setFlags(DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached)
94 | .create();
95 |
96 | cmdbuf = dk::CmdBufMaker(device)
97 | .create();
98 |
99 | cmdbuf.addMemory(cmdbuf_memblock, 0, cmdbuf_memblock.getSize());
100 |
101 | for (std::size_t i = 0; i < framebuffer_images.size(); ++i) {
102 | auto view = dk::ImageView(framebuffer_images[i]);
103 | cmdbuf.bindRenderTargets(&view);
104 | bind_fb_cmdlists[i] = cmdbuf.finishList();
105 | }
106 | }
107 |
108 | void deko_gencmdlist(const nj::Surface &surf) {
109 | std::tie(image_memblock, image) = surf.to_deko3d(device, DkImageFlags_HwCompression | DkImageFlags_Usage2DEngine);
110 |
111 | DkImageRect rect = {
112 | 0, 0, 0,
113 | std::min(static_cast(surf.width), FB_WIDTH),
114 | std::min(static_cast(surf.height), FB_HEIGHT),
115 | 1,
116 | };
117 |
118 | // TODO: Use dkCmdBufCopyImage, which is not implemented yet
119 | for (std::size_t i = 0; i < framebuffer_images.size(); ++i) {
120 | cmdbuf.setViewports(0, DkViewport(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0.0f, 1.0f));
121 | cmdbuf.setScissors(0, DkScissor(0, 0, FB_WIDTH, FB_HEIGHT));
122 | cmdbuf.clearColor(0, DkColorMask_RGBA, 0.1f, 0.1f, 0.1f);
123 | cmdbuf.blitImage(dk::ImageView(image), rect, dk::ImageView(framebuffer_images[i]), rect, 0, 0);
124 | render_cmdlists[i] = cmdbuf.finishList();
125 | }
126 | }
127 |
128 | void deko_render() {
129 | auto slot = queue.acquireImage(swapchain);
130 |
131 | queue.submitCommands(bind_fb_cmdlists[slot]);
132 | queue.submitCommands(render_cmdlists[slot]);
133 | queue.presentImage(swapchain, slot);
134 | }
135 |
136 | void deko_exit() {
137 | queue.waitIdle();
138 |
139 | queue.destroy();
140 | cmdbuf.destroy();
141 | image_memblock.destroy();
142 | cmdbuf_memblock.destroy();
143 | fb_memblock.destroy();
144 | swapchain.destroy();
145 | device.destroy();
146 | }
147 |
148 | int main(int argc, char **argv) {
149 | // Call this before dkDeviceCreate
150 | if (auto rc = nj::initialize(); rc) {
151 | std::fprintf(stderr, "Failed to initialize library: %d: %s\n", rc, std::strerror(rc));
152 | return 1;
153 | }
154 | NJ_SCOPEGUARD([] { nj::finalize(); });
155 |
156 | nj::Decoder decoder;
157 | if (auto rc = decoder.initialize(); rc) {
158 | std::fprintf(stderr, "Failed to initialize decoder: %#x\n", rc);
159 | return rc;
160 | }
161 | NJ_SCOPEGUARD([&decoder] { decoder.finalize(); });
162 |
163 | nj::Image image((argc < 2) ? "sdmc:/test.jpg" : argv[1]);
164 | if (!image.is_valid() || image.parse()) {
165 | std::perror("Invalid file");
166 | return 1;
167 | }
168 |
169 | std::printf("Image dimensions: %ux%u\n", image.width, image.height);
170 |
171 | nj::Surface surf(image.width, image.height, nj::PixelFormat::BGRA);
172 | if (auto rc = surf.allocate(); rc) {
173 | std::fprintf(stderr, "Failed to allocate surface: %#x\n", rc);
174 | return 1;
175 | }
176 |
177 | std::printf("Surface pitch: %#lx, size %#lx\n", surf.pitch, surf.size());
178 |
179 | auto start = std::chrono::system_clock::now();
180 | if (auto rc = decoder.render(image, surf, 255); rc)
181 | std::fprintf(stderr, "Failed to render image: %#x (%s)\n", rc, std::strerror(errno));
182 |
183 | std::size_t read = 0;
184 | decoder.wait(surf, &read);
185 | auto time = std::chrono::system_clock::now() - start;
186 | std::printf("Rendered in %ldµs, read bytes: %lu\n",
187 | std::chrono::duration_cast(time).count(), read);
188 |
189 | deko_init();
190 | deko_gencmdlist(surf);
191 |
192 | PadState pad;
193 | padConfigureInput(1, HidNpadStyleSet_NpadStandard);
194 | padInitializeDefault(&pad);
195 |
196 | while (appletMainLoop()) {
197 | padUpdate(&pad);
198 | if (padGetButtonsDown(&pad) & HidNpadButton_Plus)
199 | break;
200 |
201 | deko_render();
202 | }
203 |
204 | deko_exit();
205 |
206 | return 0;
207 | }
208 |
--------------------------------------------------------------------------------
/lib/image.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2021 averne
2 | //
3 | // This file is part of oss-nvjpg.
4 | //
5 | // oss-nvjpg is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // oss-nvjpg is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with oss-nvjpg. If not, see .
17 |
18 | #include
19 | #include
20 | #include
21 |
22 | #include
23 | #include
24 |
25 | namespace nj {
26 |
27 | namespace {
28 |
29 | void skip_segment(JpegSegmentHeader seg, Bitstream &bs) {
30 | bs.skip(seg.size - sizeof(seg.size));
31 | }
32 |
33 | } // namespace
34 |
35 | Image::Image(int fd) {
36 | struct stat st;
37 | if (auto rc = ::fstat(fd, &st); rc == -1) {
38 | this->valid = false;
39 | return;
40 | }
41 |
42 | this->data = std::make_shared>(st.st_size);
43 | if (auto rc = ::read(fd, this->data->data(), this->data->size()); rc == -1) {
44 | this->valid = false;
45 | return;
46 | }
47 | }
48 |
49 | JpegSegmentHeader Image::find_next_segment(Bitstream &bs) {
50 | JpegSegmentHeader hdr;
51 | do
52 | hdr.magic = bs.get();
53 | while ((hdr.magic != JpegMarker::Magic) && !bs.empty());
54 |
55 | hdr.marker = bs.get();
56 | hdr.size = bs.get_be();
57 | return hdr;
58 | }
59 |
60 | int Image::parse_app(JpegSegmentHeader seg, Bitstream &bs) {
61 | skip_segment(seg, bs);
62 | return 0;
63 | }
64 |
65 | int Image::parse_sof(JpegSegmentHeader seg, Bitstream &bs) {
66 | if (seg.size < 11)
67 | return ENODATA;
68 |
69 | this->progressive = seg.marker == JpegMarker::Sof2;
70 |
71 | this->sampling_precision = bs.get();
72 |
73 | this->height = bs.get_be();
74 | this->width = bs.get_be();
75 |
76 | this->num_components = bs.get