Skip to content

Commit 31a154f

Browse files
author
Grok Compression
committed
rate control: add opt-in progressive rate control
1 parent 8655104 commit 31a154f

12 files changed

Lines changed: 807 additions & 8 deletions

File tree

src/lib/codec/apps/GrkCompress.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ GrkRC GrkCompress::parseCommandLine(int argc, const char* argv[], CompressInitPa
852852
std::string tileParts;
853853
uint16_t rsiz;
854854

855-
bool eph, applyICC, irreversible, plt, sop, tlm;
855+
bool eph, applyICC, irreversible, plt, sop, tlm, progressiveRC;
856856

857857
auto outDirOpt = app.add_option("-a,--out-dir", outDir, "Output directory");
858858
auto rateControlAlgorithmOpt =
@@ -922,6 +922,8 @@ GrkRC GrkCompress::parseCommandLine(int argc, const char* argv[], CompressInitPa
922922
auto mctOpt = app.add_option("-Y,--mct", mct, "Multi component transform")->default_val(0);
923923
auto imfOpt = app.add_option("-z,--imf", imf, "IMF profile");
924924
auto rsizOpt = app.add_option("-Z,--rsiz", rsiz, "Rsiz")->default_val(0);
925+
auto progressiveRCOpt =
926+
app.add_flag("--progressive-rc", progressiveRC, "Progressive rate control");
925927

926928
app.set_help_flag("-h", "Show abreviated usage");
927929
app.add_flag("--help", "Show detailed usage");
@@ -962,6 +964,8 @@ GrkRC GrkCompress::parseCommandLine(int argc, const char* argv[], CompressInitPa
962964
parameters->write_plt = true;
963965
if(tlmOpt->count() > 0)
964966
parameters->write_tlm = true;
967+
if(progressiveRCOpt->count() > 0)
968+
parameters->progressive_rate_control = true;
965969
if(repetitionsOpt->count() > 0)
966970
parameters->repeats = repetitions;
967971
if(rateControlAlgorithmOpt->count() > 0)

src/lib/core/codestream/CodingParams.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ struct EncodingParams
353353
bool writeTlm_;
354354
/* rate control algorithm */
355355
uint32_t rateControlAlgorithm_;
356+
/* progressive rate control during T1 encoding */
357+
bool progressiveRateControl_;
356358
};
357359

358360
struct DecodingParams

src/lib/core/codestream/compress/CodeStreamCompress.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ bool CodeStreamCompress::init(grk_cparameters* parameters, GrkImage* image)
355355
cp_.codingParams_.enc_.writePlt_ = parameters->write_plt;
356356
cp_.codingParams_.enc_.writeTlm_ = parameters->write_tlm;
357357
cp_.codingParams_.enc_.rateControlAlgorithm_ = parameters->rate_control_algorithm;
358+
cp_.codingParams_.enc_.progressiveRateControl_ = parameters->progressive_rate_control;
358359

359360
/* tiles */
360361
cp_.t_width_ = parameters->t_width;

src/lib/core/grok.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,21 @@ typedef struct _grk_cparameters
17001700
uint8_t num_rreq_standard_features; /* number of standard features */
17011701
bool geoboxes_after_jp2c; /* write metadata boxes (UUID, asoc, XML) after codestream */
17021702

1703+
/**
1704+
* Enable progressive rate control during T1 encoding.
1705+
*
1706+
* When true, the encoder estimates the PCRD slope threshold progressively
1707+
* as code blocks complete, and terminates encoding of subsequent blocks early
1708+
* when their coding passes are predicted to be discarded by rate control.
1709+
*
1710+
* This provides significant speedup (20-40% for DCI/high-ratio compression)
1711+
* at the cost of non-bit-exact output compared to standard PCRD. Quality
1712+
* differences are negligible (within rounding error of the target rate).
1713+
*
1714+
* Default: false (standard full-encode + PCRD, bit-exact reproducible output).
1715+
*/
1716+
bool progressive_rate_control;
1717+
17031718
/* Transcode mode: rewrite JP2 boxes while copying the codestream verbatim.
17041719
* Set transcode=true and populate transcode_src with the source stream.
17051720
* The image passed to grk_transcode() provides the metadata for the new boxes.

src/lib/core/scheduling/standard/CompressScheduler.cpp

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ struct ITileProcessor;
5959
namespace grk
6060
{
6161
CompressScheduler::CompressScheduler(Tile* tile, bool needsRateControl, TileCodingParams* tcp,
62-
const double* mct_norms, uint16_t mct_numcomps)
62+
const double* mct_norms, uint16_t mct_numcomps,
63+
bool progressiveRateControl)
6364
: SchedulerStandard(tile->numcomps_), tile_(tile), needsRateControl_(needsRateControl),
65+
progressiveRateControl_(progressiveRateControl),
6466
blockCount_(-1), tcp_(tcp), mct_norms_(mct_norms), mct_numcomps_(mct_numcomps)
6567
{
6668
rateControlStats_.init(tile->numcomps_);
@@ -135,6 +137,11 @@ bool CompressScheduler::scheduleT1(ITileProcessor* proc)
135137
encodeBlocks_ = blocks;
136138
const size_t maxBlocks = blocks.size();
137139

140+
// Initialize progressive slope estimator when rate control is active.
141+
// The estimator needs: total samples (sum of all block areas) and target rate (bytes/sample).
142+
// This enables early termination of coding passes predicted to be discarded by PCRD.
143+
initSlopeEstimator(blocks);
144+
138145
tf::Taskflow taskflow;
139146
size_t num_threads = TFSingleton::num_threads();
140147
auto node = new tf::Task[num_threads];
@@ -223,6 +230,9 @@ bool CompressScheduler::populateT1Flow(FlowComponent* flow)
223230
encodeBlocks_ = blocks;
224231
const size_t maxBlocks = blocks.size();
225232

233+
// Initialize progressive slope estimator (same as scheduleT1 path)
234+
initSlopeEstimator(blocks);
235+
226236
size_t num_threads = TFSingleton::num_threads();
227237
for(auto i = 0U; i < num_threads; i++)
228238
{
@@ -244,6 +254,12 @@ bool CompressScheduler::compress(size_t workerId, uint64_t maxBlocks)
244254
if(index >= maxBlocks)
245255
return false;
246256
auto block = encodeBlocks_[index];
257+
258+
// Set the early-stop threshold from the progressive estimator.
259+
// This is a lock-free atomic read — the estimator updates it as blocks complete.
260+
if(slopeEstimator_)
261+
block->earlyStopSlope = slopeEstimator_->getEarlyStopSlope();
262+
247263
compress(coder, block);
248264
delete block;
249265

@@ -270,6 +286,24 @@ void CompressScheduler::compress(t1::ICoder* coder, t1::CompressBlockExec* block
270286
if(cblk->getNumPasses() > 0)
271287
RateControl::convexHull(cblk->getPass(0), cblk->getNumPasses());
272288

289+
// Feed completed block data to the progressive slope estimator.
290+
// This builds the slope-rate histogram used to predict the PCRD threshold.
291+
if(slopeEstimator_ && cblk->getNumPasses() > 0)
292+
{
293+
// Extract slopes and rates into stack arrays for the estimator.
294+
// Maximum coding passes per block: 3 * maxBitPlanes ≈ 3*16 = 48.
295+
uint8_t numPasses = cblk->getNumPasses();
296+
uint16_t slopes[48];
297+
uint16_t rates[48];
298+
for(uint8_t p = 0; p < numPasses; p++)
299+
{
300+
auto pass = cblk->getPass(p);
301+
slopes[p] = pass->slope_;
302+
rates[p] = pass->rate_;
303+
}
304+
slopeEstimator_->updateStats(slopes, rates, numPasses, num_pix);
305+
}
306+
273307
// Collect slope stats for both bisect algorithms
274308
for(uint8_t passno = 0; passno < cblk->getNumPasses(); passno++)
275309
{
@@ -305,4 +339,45 @@ void CompressScheduler::compress(t1::ICoder* coder, t1::CompressBlockExec* block
305339
}
306340
}
307341

342+
void CompressScheduler::initSlopeEstimator(
343+
const std::vector<t1::CompressBlockExec*>& blocks)
344+
{
345+
// The progressive slope estimator requires:
346+
// 1. A target rate is specified (rate-distortion mode)
347+
// 2. Rate control is needed (not lossless, not zero-rate)
348+
//
349+
// For multi-layer encoding, we must use the HIGHEST quality layer's target
350+
// (the largest byte budget). This is the most permissive threshold — it
351+
// determines the minimum slope below which passes are DEFINITELY discarded
352+
// by ALL layers. Using a smaller layer's target would over-estimate the
353+
// threshold and incorrectly terminate passes needed by higher-quality layers.
354+
//
355+
// tcp_->rates_[k] contains target byte counts after conversion from
356+
// compression ratios. Higher indices = higher quality = more bytes.
357+
if(!needsRateControl_)
358+
return;
359+
if(!progressiveRateControl_)
360+
return;
361+
362+
// Find the maximum target rate across all layers
363+
double maxTargetBytes = 0.0;
364+
for(uint16_t k = 0; k < tcp_->numLayers_; ++k)
365+
{
366+
if(tcp_->rates_[k] > maxTargetBytes)
367+
maxTargetBytes = tcp_->rates_[k];
368+
}
369+
if(maxTargetBytes <= 0.0)
370+
return;
371+
372+
uint64_t totalSamples = 0;
373+
for(const auto* blk : blocks)
374+
totalSamples += static_cast<uint64_t>(blk->cblk->width()) * blk->cblk->height();
375+
376+
if(totalSamples == 0)
377+
return;
378+
379+
double targetRate = maxTargetBytes / static_cast<double>(totalSamples);
380+
slopeEstimator_ = std::make_unique<ProgressiveSlopeEstimator>(totalSamples, targetRate);
381+
}
382+
308383
} // namespace grk

src/lib/core/scheduling/standard/CompressScheduler.h

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
#include "SchedulerStandard.h"
2121
#include "RateControlStats.h"
22+
#include "ProgressiveSlopeEstimator.h"
23+
#include <memory>
2224

2325
namespace grk
2426
{
@@ -34,7 +36,8 @@ class CompressScheduler : public SchedulerStandard
3436
* @param mct_numcomps number of mct components
3537
*/
3638
CompressScheduler(Tile* tile, bool needsRateControl, TileCodingParams* tcp,
37-
const double* mct_norms, uint16_t mct_numcomps);
39+
const double* mct_norms, uint16_t mct_numcomps,
40+
bool progressiveRateControl = false);
3841
/**
3942
* @brief Destroys a CompressScheduler
4043
*/
@@ -69,6 +72,16 @@ class CompressScheduler : public SchedulerStandard
6972
*/
7073
void compress(t1::ICoder* coder, t1::CompressBlockExec* block);
7174

75+
/**
76+
* @brief Initialize the progressive slope estimator from the block list.
77+
*
78+
* Computes total sample count across all blocks and target rate (bytes/sample),
79+
* then constructs the ProgressiveSlopeEstimator if a rate target is available.
80+
*
81+
* @param blocks Vector of all blocks to be encoded in this tile.
82+
*/
83+
void initSlopeEstimator(const std::vector<t1::CompressBlockExec*>& blocks);
84+
7285
/**
7386
* @brief @ref Tile to compress
7487
*/
@@ -82,6 +95,11 @@ class CompressScheduler : public SchedulerStandard
8295
* @brief true if rate control requested
8396
*/
8497
bool needsRateControl_;
98+
99+
/**
100+
* @brief true if progressive rate control (early termination) is enabled
101+
*/
102+
bool progressiveRateControl_;
85103
/**
86104
* @brief vector of @ref CompressBlockExec encode blocks
87105
*/
@@ -111,6 +129,18 @@ class CompressScheduler : public SchedulerStandard
111129
* @brief rate control statistics collected during T1 encoding
112130
*/
113131
RateControlStats rateControlStats_;
132+
133+
/**
134+
* @brief Progressive slope estimator for intra-frame early pass termination.
135+
*
136+
* When a target rate is known, this estimator builds a slope histogram from
137+
* completed blocks and publishes a conservative early-stop threshold that
138+
* subsequent blocks can use to skip encoding trailing bit-planes that would
139+
* be discarded by PCRD.
140+
*
141+
* Null when rate control is not applicable (lossless, no rate target).
142+
*/
143+
std::unique_ptr<ProgressiveSlopeEstimator> slopeEstimator_;
114144
};
115145

116146
} // namespace grk

src/lib/core/t1/BlockExec.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ struct CompressBlockExec : public BlockExec
155155
uint16_t mct_numcomps = 0;
156156
bool use16BitDwt = false;
157157

158+
/**
159+
* @brief Progressive early-stop slope threshold (log-domain, uint16_t).
160+
*
161+
* When non-zero, the block encoder may terminate encoding after a
162+
* complete bit-plane if the incremental slope of that bit-plane falls
163+
* below this threshold. This value is set by the ProgressiveSlopeEstimator
164+
* based on statistics from previously-completed blocks in the same tile.
165+
*
166+
* A value of 0 means "no early termination" — encode all bit-planes.
167+
*/
168+
uint16_t earlyStopSlope = 0;
169+
158170
// Delete copy constructor and assignment operator
159171
CompressBlockExec(const CompressBlockExec&) = delete;
160172
CompressBlockExec& operator=(const CompressBlockExec&) = delete;

src/lib/core/t1/part1/Coder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ bool Coder::compress(CompressBlockExec* block)
141141
auto distortion =
142142
blockCoder_->compress_cblk(&cblkexp, max, block->bandOrientation, block->compno, block->level,
143143
block->qmfbid, block->stepsize, block->cblk_sty, block->mct_norms,
144-
block->mct_numcomps, block->doRateControl);
144+
block->mct_numcomps, block->doRateControl, block->earlyStopSlope);
145145

146146
cblk->setNumPasses(cblkexp.numPassesTotal);
147147
cblk->setNumBps(cblkexp.numbps);

src/lib/core/t1/part1/block_coder/BlockCoder.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class BlockCoder
3636
void code_block_enc_deallocate(cblk_enc* p_code_block);
3737
double compress_cblk(cblk_enc* cblk, uint32_t max, uint8_t orientation, uint16_t compno,
3838
uint8_t level, uint8_t qmfbid, double stepsize, uint32_t cblksty,
39-
const double* mct_norms, uint16_t mct_numcomps, bool doRateControl);
39+
const double* mct_norms, uint16_t mct_numcomps, bool doRateControl,
40+
uint16_t earlyStopSlope = 0);
4041
static double getnorm(uint32_t level, uint8_t orientation, bool reversible);
4142

4243
bool decompress_cblk(CodeblockDecompress* cblk, uint8_t orientation, uint32_t cblksty);

0 commit comments

Comments
 (0)