blob: 8648663816ac7de2333905a07079089874fab46d [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/blitter.h"
#include "starboard/log.h"
namespace {
// Round the given float to the nearest integer.
int round(float x) {
return static_cast<int>(x + 0.5f);
}
// A periodic function that follows the same sawtooth pattern as the modulus
// function in both the postive and negative domain.
int positive_mod(int x, int m) {
if (x >= 0) {
return x % m;
} else {
int mod_out = (1 - (x / m)) * m + x;
return mod_out == m ? 0 : mod_out;
}
}
// The Tiler class is a helper class to determine how to chop up into tiles a
// source image into a destination rectangle. The Tiler will pre-compute all
// the information needed to determine coordinates of individual tiles. This
// makes it easy to have every SbBlitterBlitRectToRectTiled() call implemented
// by a SbBlitterBlitRectsToRects() call with one rect per tile.
class Tiler {
public:
Tiler(const SbBlitterRect& src_rect,
const SbBlitterRect& dst_rect,
int src_image_width,
int src_image_height);
// Returns the number of tiles in the respective dimensions, including partial
// tiles.
int num_x_tiles() const;
int num_y_tiles() const;
// Returns the rectangles for the source rectangle for the given tile
// coordinate.
SbBlitterRect src_rect(int x, int y) const;
// Returns the rectangles for the destination rectangle for the given tile
// coordinate.
SbBlitterRect dst_rect(int x, int y) const;
private:
float dst_x(int x_index) const;
float dst_y(int y_index) const;
// Saved input parameters.
int source_image_width_;
int source_image_height_;
SbBlitterRect dst_rect_;
// The positions within the source image of the first tile, where we start.
int src_first_x_;
int src_first_y_;
// The positions within the source image of the last tile, where we end.
int src_last_y_;
int src_last_x_;
// The number of tiles in each direction, counting partial tiles.
int num_x_tiles_;
int num_y_tiles_;
// The size of the first (possibly partial) tile, in destination pixels.
float dst_x_first_width_;
float dst_y_first_height_;
// The size of a full tile, in destination pixels.
float dst_tile_width_;
float dst_tile_height_;
};
Tiler::Tiler(const SbBlitterRect& src_rect,
const SbBlitterRect& dst_rect,
int src_image_width,
int src_image_height) {
// Precompute all our helper values here in the constructor.
source_image_width_ = src_image_width;
source_image_height_ = src_image_height;
dst_rect_ = dst_rect;
src_first_x_ = positive_mod(src_rect.x, src_image_width);
int num_x_full_tiles =
(src_rect.width -
(src_first_x_ != 0 ? (src_image_width - src_first_x_) : 0)) /
src_image_width;
src_last_x_ = positive_mod(src_rect.width - (src_image_width - src_first_x_),
src_image_width);
if (src_last_x_ == 0) {
src_last_x_ = src_image_width;
}
num_x_tiles_ =
(num_x_full_tiles > 0
? num_x_full_tiles + (src_first_x_ != 0 ? 1 : 0) +
(src_last_x_ != src_image_width ? 1 : 0)
: (src_first_x_ + src_rect.width > src_image_width ? 2 : 1));
src_first_y_ = positive_mod(src_rect.y, src_image_height);
int num_y_full_tiles =
(src_rect.height -
(src_first_y_ != 0 ? (src_image_height - src_first_y_) : 0)) /
src_image_height;
src_last_y_ = positive_mod(
src_rect.height - (src_image_height - src_first_y_), src_image_height);
if (src_last_y_ == 0) {
src_last_y_ = src_image_height;
}
num_y_tiles_ =
(num_y_full_tiles > 0
? num_y_full_tiles + (src_first_y_ != 0 ? 1 : 0) +
(src_last_y_ != src_image_height ? 1 : 0)
: (src_first_y_ + src_rect.height > src_image_height ? 2 : 1));
dst_tile_width_ =
(static_cast<float>(src_image_width) / src_rect.width) * dst_rect.width;
dst_x_first_width_ =
dst_tile_width_ *
(static_cast<float>(src_image_width - src_first_x_) / src_image_width);
dst_tile_height_ = (static_cast<float>(src_image_height) / src_rect.height) *
dst_rect.height;
dst_y_first_height_ =
dst_tile_height_ *
(static_cast<float>(src_image_height - src_first_y_) / src_image_height);
}
int Tiler::num_x_tiles() const {
return num_x_tiles_;
}
int Tiler::num_y_tiles() const {
return num_y_tiles_;
}
SbBlitterRect Tiler::src_rect(int x, int y) const {
SbBlitterRect ret;
ret.x = (x == 0 ? src_first_x_ : 0);
ret.width =
(x == num_x_tiles() - 1 ? src_last_x_ : source_image_width_) - ret.x;
ret.y = (y == 0 ? src_first_y_ : 0);
ret.height =
(y == num_y_tiles() - 1 ? src_last_y_ : source_image_height_) - ret.y;
return ret;
}
SbBlitterRect Tiler::dst_rect(int x, int y) const {
SbBlitterRect ret;
int x1 = round(dst_x(x));
int x2 = round(dst_x(x + 1));
int y1 = round(dst_y(y));
int y2 = round(dst_y(y + 1));
ret.x = x1;
ret.y = y1;
ret.width = x2 - x1;
ret.height = y2 - y1;
return ret;
}
float Tiler::dst_x(int x_index) const {
if (x_index == 0) {
return static_cast<float>(dst_rect_.x);
} else if (x_index == num_x_tiles()) {
return static_cast<float>(dst_rect_.x + dst_rect_.width);
} else if (x_index == 1) {
return dst_rect_.x + dst_x_first_width_;
} else {
return dst_rect_.x + dst_x_first_width_ + dst_tile_width_ * (x_index - 1);
}
}
float Tiler::dst_y(int y_index) const {
if (y_index == 0) {
return static_cast<float>(dst_rect_.y);
} else if (y_index == num_y_tiles()) {
return static_cast<float>(dst_rect_.y + dst_rect_.height);
} else if (y_index == 1) {
return dst_rect_.y + dst_y_first_height_;
} else {
return dst_rect_.y + dst_y_first_height_ + dst_tile_height_ * (y_index - 1);
}
}
} // namespace
// Implement the tiling by splitting our draw area into individual tiles
// and rendering each one explicitly via SbBlitterBlitRectsToRects().
bool SbBlitterBlitRectToRectTiled(SbBlitterContext context,
SbBlitterSurface source_surface,
SbBlitterRect src_rect,
SbBlitterRect dst_rect) {
if (!SbBlitterIsContextValid(context)) {
SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid context.";
return false;
}
if (!SbBlitterIsSurfaceValid(source_surface)) {
SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid texture.";
return false;
}
if (src_rect.width <= 0 || src_rect.height <= 0) {
SB_DLOG(ERROR) << __FUNCTION__ << ": Source width and height must both be "
<< "greater than 0.";
return false;
}
if (dst_rect.width < 0 || dst_rect.height < 0) {
SB_DLOG(ERROR) << __FUNCTION__ << ": Destination width and height must "
<< "both be greater than or equal to 0.";
return false;
}
SbBlitterSurfaceInfo surface_info;
SbBlitterGetSurfaceInfo(source_surface, &surface_info);
Tiler tiler(src_rect, dst_rect, surface_info.width, surface_info.height);
int num_tiles = tiler.num_x_tiles() * tiler.num_y_tiles();
SbBlitterRect* src_rects = new SbBlitterRect[num_tiles];
SbBlitterRect* dst_rects = new SbBlitterRect[num_tiles];
int current_tile = 0;
for (int y = 0; y < tiler.num_y_tiles(); ++y) {
for (int x = 0; x < tiler.num_x_tiles(); ++x) {
src_rects[current_tile] = tiler.src_rect(x, y);
dst_rects[current_tile] = tiler.dst_rect(x, y);
++current_tile;
}
}
bool result = SbBlitterBlitRectsToRects(context, source_surface, src_rects,
dst_rects, num_tiles);
delete[] src_rects;
delete[] dst_rects;
return result;
}