7fba7b58ed0a285bc770cfa4570fd9921f366f52
[dcpomatic.git] / src / lib / cuda_j2k_frame_encoder.cc
1 /*
2     Copyright (C) 2022 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "cross.h"
23 #include "cuda_j2k_frame_encoder.h"
24 #include "dcpomatic_log.h"
25 #include "dcp_video.h"
26 #include "exceptions.h"
27 #include "player_video.h"
28 #include <dcp/openjpeg_image.h>
29 #include <nvjpeg2k.h>
30 #include <vector>
31
32
33 using std::make_pair;
34 using std::vector;
35 using boost::optional;
36
37
38 CUDAJ2KFrameEncoder::CUDAJ2KFrameEncoder()
39 {
40         nvjpeg2kEncoderCreateSimple(&_encoder_handle);
41         nvjpeg2kEncodeStateCreate(_encoder_handle, &_encoder_state);
42         nvjpeg2kEncodeParamsCreate(&_encoder_params);
43
44         cudaStreamCreateWithFlags(&_stream, cudaStreamNonBlocking);
45 }
46
47
48 CUDAJ2KFrameEncoder::Input::Input(DCPVideo const& vf)
49         : _index(vf.index())
50         , _eyes(vf.eyes())
51 {
52         auto xyz = convert_to_xyz(vf.frame(), boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2));
53
54         void* pixel_data_h[] = {
55                 xyz->data(0),
56                 xyz->data(1),
57                 xyz->data(2)
58         };
59
60         auto const pitch = xyz->size().width * 2;
61
62         for (int i = 0; i < 3; ++i) {
63                 _pitch_in_bytes[i] = pitch;
64                 auto status = cudaMallocPitch(
65                         reinterpret_cast<void**>(&_pixel_data_d[i]),
66                         &_pitch_in_bytes[i],
67                         pitch,
68                         xyz->size().height
69                         );
70
71                 if (status != cudaSuccess) {
72                         throw CUDAError("cudaMallocPitch", status);
73                 }
74
75                 status = cudaMemcpy2D(
76                         _pixel_data_d[i],
77                         _pitch_in_bytes[i],
78                         pixel_data_h[i],
79                         _pitch_in_bytes[i],
80                         pitch,
81                         xyz->size().height,
82                         cudaMemcpyHostToDevice
83                         );
84
85                 if (status != cudaSuccess) {
86                         throw CUDAError("cudaMemcpy2D", status);
87                 }
88         }
89
90         _device_image.num_components = 3;
91         _device_image.pixel_data = reinterpret_cast<void**>(_pixel_data_d);
92         _device_image.pixel_type = NVJPEG2K_UINT16;
93         _device_image.pitch_in_bytes = reinterpret_cast<size_t*>(_pitch_in_bytes);
94 }
95
96
97 CUDAJ2KFrameEncoder::Input::Input(Input&& other)
98         : _index(other._index)
99         , _eyes(other._eyes)
100 {
101         for (int i = 0; i < 3; ++i) {
102                 _pixel_data_d[i] = other._pixel_data_d[i];
103                 other._pixel_data_d[i] = nullptr;
104                 _pitch_in_bytes[i] = other._pitch_in_bytes[i];
105         }
106
107         _device_image.num_components = other._device_image.num_components;
108         _device_image.pixel_data = reinterpret_cast<void**>(_pixel_data_d);
109         _device_image.pixel_type = NVJPEG2K_UINT16;
110         _device_image.pitch_in_bytes = reinterpret_cast<size_t*>(_pitch_in_bytes);
111 }
112
113
114 CUDAJ2KFrameEncoder::Input::~Input()
115 {
116         cudaFree(_pixel_data_d[0]);
117         cudaFree(_pixel_data_d[1]);
118         cudaFree(_pixel_data_d[2]);
119 }
120
121
122 optional<dcp::ArrayData>
123 CUDAJ2KFrameEncoder::encode(DCPVideo const& vf)
124 {
125         auto input = Input(vf);
126
127         auto const size = vf.frame()->out_size();
128         DCPOMATIC_ASSERT(!_size || size == *_size);
129         _size = size;
130
131         DCPOMATIC_ASSERT(!_resolution || vf.resolution() == *_resolution);
132         _resolution = vf.resolution();
133
134         nvjpeg2kImageComponentInfo_t info[3];
135         for (int i = 0; i < 3; ++i) {
136                 info[i].component_width = _size->width;
137                 info[i].component_height = _size->height;
138                 info[i].precision = 12;
139                 info[i].sgn = 0;
140         }
141
142         nvjpeg2kEncodeConfig_t config;
143         memset(&config, 0, sizeof(config));
144         config.stream_type = NVJPEG2K_STREAM_J2K;
145         config.color_space = NVJPEG2K_COLORSPACE_SRGB;
146         config.image_width = _size->width;
147         config.image_height = _size->height;
148         config.num_components = 3;
149         config.image_comp_info = reinterpret_cast<nvjpeg2kImageComponentInfo_t*>(&info);
150         config.code_block_w = 32;
151         config.code_block_h = 32;
152         config.irreversible = 0;
153         config.mct_mode = 1;
154         config.prog_order = NVJPEG2K_CPRL;
155         config.num_resolutions = *_resolution == Resolution::FOUR_K ? 7 : 6;
156
157         auto status = nvjpeg2kEncodeParamsSetEncodeConfig(_encoder_params, &config);
158         if (status != NVJPEG2K_STATUS_SUCCESS) {
159                 throw CUDAError("nvjpeg2kEncodeParamsSetEncodeConfig", status);
160         }
161
162         // XXX: quality
163         status = nvjpeg2kEncodeParamsSetQuality(_encoder_params, 30);
164         if (status != NVJPEG2K_STATUS_SUCCESS) {
165                 throw CUDAError("nvjpeg2kEncodeParamsSetQuality", status);
166         }
167
168         status = nvjpeg2kEncode(_encoder_handle, _encoder_state, _encoder_params, input.device_image(), _stream);
169         if (status != NVJPEG2K_STATUS_SUCCESS) {
170                 throw CUDAError("nvjpeg2kEncode", status);
171         }
172
173         size_t compressed_size;
174         status = nvjpeg2kEncodeRetrieveBitstream(_encoder_handle, _encoder_state, nullptr, &compressed_size, _stream);
175
176         dcp::ArrayData output(compressed_size);
177         status = nvjpeg2kEncodeRetrieveBitstream(_encoder_handle, _encoder_state, output.data(), &compressed_size, _stream);
178         if (status != NVJPEG2K_STATUS_SUCCESS) {
179                 throw CUDAError("nvjpeg2kEncodeRetrieveBitstream", status);
180         }
181
182         return output;
183 }
184
185
186 void
187 CUDAJ2KFrameEncoder::log_thread_start ()
188 {
189        LOG_TIMING("start-encoder-thread thread=%1", thread_id());
190 }
191
192
193 void
194 CUDAJ2KFrameEncoder::flush()
195 {
196
197 }