Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: cc/output/gl_renderer.cc

Issue 2543473004: cc: Move filters from RenderPassDrawQuad to RenderPass (Closed)
Patch Set: Rebase again Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « cc/output/gl_renderer.h ('k') | cc/output/gl_renderer_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2010 The Chromium Authors. All rights reserved. 1 // Copyright 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "cc/output/gl_renderer.h" 5 #include "cc/output/gl_renderer.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 #include <stdint.h> 8 #include <stdint.h>
9 9
10 #include <algorithm> 10 #include <algorithm>
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after
160 ~DrawRenderPassDrawQuadParams() {} 160 ~DrawRenderPassDrawQuadParams() {}
161 161
162 // Required Inputs. 162 // Required Inputs.
163 const RenderPassDrawQuad* quad = nullptr; 163 const RenderPassDrawQuad* quad = nullptr;
164 const Resource* contents_texture = nullptr; 164 const Resource* contents_texture = nullptr;
165 const gfx::QuadF* clip_region = nullptr; 165 const gfx::QuadF* clip_region = nullptr;
166 bool flip_texture = false; 166 bool flip_texture = false;
167 gfx::Transform window_matrix; 167 gfx::Transform window_matrix;
168 gfx::Transform projection_matrix; 168 gfx::Transform projection_matrix;
169 gfx::Transform quad_to_target_transform; 169 gfx::Transform quad_to_target_transform;
170 const FilterOperations* filters = nullptr;
171 const FilterOperations* background_filters = nullptr;
170 172
171 // |frame| is only used for background effects. 173 // |frame| is only used for background effects.
172 DirectRenderer::DrawingFrame* frame = nullptr; 174 DirectRenderer::DrawingFrame* frame = nullptr;
173 175
174 // Whether the texture to be sampled from needs to be flipped. 176 // Whether the texture to be sampled from needs to be flipped.
175 bool source_needs_flip = false; 177 bool source_needs_flip = false;
176 178
177 float edge[24]; 179 float edge[24];
178 SkScalar color_matrix[20]; 180 SkScalar color_matrix[20];
179 181
(...skipping 605 matching lines...) Expand 10 before | Expand all | Expand 10 after
785 if (blend_mode == SkBlendMode::kSrcOver) 787 if (blend_mode == SkBlendMode::kSrcOver)
786 return; 788 return;
787 789
788 if (use_blend_equation_advanced_) { 790 if (use_blend_equation_advanced_) {
789 gl_->BlendEquation(GL_FUNC_ADD); 791 gl_->BlendEquation(GL_FUNC_ADD);
790 } else { 792 } else {
791 gl_->BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 793 gl_->BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
792 } 794 }
793 } 795 }
794 796
795 bool GLRenderer::ShouldApplyBackgroundFilters(const RenderPassDrawQuad* quad) { 797 bool GLRenderer::ShouldApplyBackgroundFilters(
796 if (quad->background_filters.IsEmpty()) 798 const RenderPassDrawQuad* quad,
799 const FilterOperations* background_filters) {
800 if (!background_filters)
797 return false; 801 return false;
802 DCHECK(!background_filters->IsEmpty());
798 803
799 // TODO(hendrikw): Look into allowing background filters to see pixels from 804 // TODO(hendrikw): Look into allowing background filters to see pixels from
800 // other render targets. See crbug.com/314867. 805 // other render targets. See crbug.com/314867.
801 806
802 return true; 807 return true;
803 } 808 }
804 809
805 // This takes a gfx::Rect and a clip region quad in the same space, 810 // This takes a gfx::Rect and a clip region quad in the same space,
806 // and returns a quad with the same proportions in the space -0.5->0.5. 811 // and returns a quad with the same proportions in the space -0.5->0.5.
807 bool GetScaledRegion(const gfx::Rect& rect, 812 bool GetScaledRegion(const gfx::Rect& rect,
(...skipping 28 matching lines...) Expand all
836 uvs[5] = ((clip->p3().y() - rect.y()) / rect.height()); 841 uvs[5] = ((clip->p3().y() - rect.y()) / rect.height());
837 uvs[6] = ((clip->p4().x() - rect.x()) / rect.width()); 842 uvs[6] = ((clip->p4().x() - rect.x()) / rect.width());
838 uvs[7] = ((clip->p4().y() - rect.y()) / rect.height()); 843 uvs[7] = ((clip->p4().y() - rect.y()) / rect.height());
839 return true; 844 return true;
840 } 845 }
841 846
842 gfx::Rect GLRenderer::GetBackdropBoundingBoxForRenderPassQuad( 847 gfx::Rect GLRenderer::GetBackdropBoundingBoxForRenderPassQuad(
843 DrawingFrame* frame, 848 DrawingFrame* frame,
844 const RenderPassDrawQuad* quad, 849 const RenderPassDrawQuad* quad,
845 const gfx::Transform& contents_device_transform, 850 const gfx::Transform& contents_device_transform,
851 const FilterOperations* filters,
852 const FilterOperations* background_filters,
846 const gfx::QuadF* clip_region, 853 const gfx::QuadF* clip_region,
847 bool use_aa, 854 bool use_aa,
848 gfx::Rect* unclipped_rect) { 855 gfx::Rect* unclipped_rect) {
849 gfx::QuadF scaled_region; 856 gfx::QuadF scaled_region;
850 if (!GetScaledRegion(quad->rect, clip_region, &scaled_region)) { 857 if (!GetScaledRegion(quad->rect, clip_region, &scaled_region)) {
851 scaled_region = SharedGeometryQuad().BoundingBox(); 858 scaled_region = SharedGeometryQuad().BoundingBox();
852 } 859 }
853 860
854 gfx::Rect backdrop_rect = gfx::ToEnclosingRect(MathUtil::MapClippedRect( 861 gfx::Rect backdrop_rect = gfx::ToEnclosingRect(MathUtil::MapClippedRect(
855 contents_device_transform, scaled_region.BoundingBox())); 862 contents_device_transform, scaled_region.BoundingBox()));
856 863
857 if (ShouldApplyBackgroundFilters(quad)) { 864 if (ShouldApplyBackgroundFilters(quad, background_filters)) {
858 SkMatrix matrix; 865 SkMatrix matrix;
859 matrix.setScale(quad->filters_scale.x(), quad->filters_scale.y()); 866 matrix.setScale(quad->filters_scale.x(), quad->filters_scale.y());
860 if (FlippedFramebuffer(frame)) { 867 if (FlippedFramebuffer(frame)) {
861 // TODO(jbroman): This probably isn't the right way to account for this. 868 // TODO(jbroman): This probably isn't the right way to account for this.
862 // Probably some combination of frame->projection_matrix, 869 // Probably some combination of frame->projection_matrix,
863 // frame->window_matrix and contents_device_transform? 870 // frame->window_matrix and contents_device_transform?
864 matrix.postScale(1, -1); 871 matrix.postScale(1, -1);
865 } 872 }
866 backdrop_rect = 873 backdrop_rect = background_filters->MapRectReverse(backdrop_rect, matrix);
867 quad->background_filters.MapRectReverse(backdrop_rect, matrix);
868 } 874 }
869 875
870 if (!backdrop_rect.IsEmpty() && use_aa) { 876 if (!backdrop_rect.IsEmpty() && use_aa) {
871 const int kOutsetForAntialiasing = 1; 877 const int kOutsetForAntialiasing = 1;
872 backdrop_rect.Inset(-kOutsetForAntialiasing, -kOutsetForAntialiasing); 878 backdrop_rect.Inset(-kOutsetForAntialiasing, -kOutsetForAntialiasing);
873 } 879 }
874 880
875 if (!quad->filters.IsEmpty()) { 881 if (filters) {
882 DCHECK(!filters->IsEmpty());
876 // If we have filters, grab an extra one-pixel border around the 883 // If we have filters, grab an extra one-pixel border around the
877 // background, so texture edge clamping gives us a transparent border 884 // background, so texture edge clamping gives us a transparent border
878 // in case the filter expands the result. 885 // in case the filter expands the result.
879 backdrop_rect.Inset(-1, -1, -1, -1); 886 backdrop_rect.Inset(-1, -1, -1, -1);
880 } 887 }
881 888
882 *unclipped_rect = backdrop_rect; 889 *unclipped_rect = backdrop_rect;
883 backdrop_rect.Intersect(MoveFromDrawToWindowSpace( 890 backdrop_rect.Intersect(MoveFromDrawToWindowSpace(
884 frame, frame->current_render_pass->output_rect)); 891 frame, frame->current_render_pass->output_rect));
885 return backdrop_rect; 892 return backdrop_rect;
(...skipping 11 matching lines...) Expand all
897 { 904 {
898 ResourceProvider::ScopedWriteLockGL lock( 905 ResourceProvider::ScopedWriteLockGL lock(
899 resource_provider_, device_background_texture->id(), false); 906 resource_provider_, device_background_texture->id(), false);
900 GetFramebufferTexture(lock.texture_id(), bounding_rect); 907 GetFramebufferTexture(lock.texture_id(), bounding_rect);
901 } 908 }
902 return device_background_texture; 909 return device_background_texture;
903 } 910 }
904 911
905 sk_sp<SkImage> GLRenderer::ApplyBackgroundFilters( 912 sk_sp<SkImage> GLRenderer::ApplyBackgroundFilters(
906 const RenderPassDrawQuad* quad, 913 const RenderPassDrawQuad* quad,
914 const FilterOperations& background_filters,
907 ScopedResource* background_texture, 915 ScopedResource* background_texture,
908 const gfx::RectF& rect, 916 const gfx::RectF& rect,
909 const gfx::RectF& unclipped_rect) { 917 const gfx::RectF& unclipped_rect) {
910 DCHECK(ShouldApplyBackgroundFilters(quad)); 918 DCHECK(ShouldApplyBackgroundFilters(quad, &background_filters));
911 auto use_gr_context = ScopedUseGrContext::Create(this); 919 auto use_gr_context = ScopedUseGrContext::Create(this);
912 920
913 gfx::Vector2dF clipping_offset = 921 gfx::Vector2dF clipping_offset =
914 (rect.top_right() - unclipped_rect.top_right()) + 922 (rect.top_right() - unclipped_rect.top_right()) +
915 (rect.bottom_left() - unclipped_rect.bottom_left()); 923 (rect.bottom_left() - unclipped_rect.bottom_left());
916 sk_sp<SkImageFilter> filter = RenderSurfaceFilters::BuildImageFilter( 924 sk_sp<SkImageFilter> filter = RenderSurfaceFilters::BuildImageFilter(
917 quad->background_filters, gfx::SizeF(background_texture->size()), 925 background_filters, gfx::SizeF(background_texture->size()),
918 clipping_offset); 926 clipping_offset);
919 927
920 // TODO(senorblanco): background filters should be moved to the 928 // TODO(senorblanco): background filters should be moved to the
921 // makeWithFilter fast-path, and go back to calling ApplyImageFilter(). 929 // makeWithFilter fast-path, and go back to calling ApplyImageFilter().
922 // See http://crbug.com/613233. 930 // See http://crbug.com/613233.
923 if (!filter || !use_gr_context) 931 if (!filter || !use_gr_context)
924 return nullptr; 932 return nullptr;
925 933
926 ResourceProvider::ScopedReadLockGL lock(resource_provider_, 934 ResourceProvider::ScopedReadLockGL lock(resource_provider_,
927 background_texture->id()); 935 background_texture->id());
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
1083 UpdateRPDQUniforms(params); 1091 UpdateRPDQUniforms(params);
1084 DrawRPDQ(*params); 1092 DrawRPDQ(*params);
1085 } 1093 }
1086 1094
1087 bool GLRenderer::InitializeRPDQParameters( 1095 bool GLRenderer::InitializeRPDQParameters(
1088 DrawRenderPassDrawQuadParams* params) { 1096 DrawRenderPassDrawQuadParams* params) {
1089 const RenderPassDrawQuad* quad = params->quad; 1097 const RenderPassDrawQuad* quad = params->quad;
1090 SkMatrix local_matrix; 1098 SkMatrix local_matrix;
1091 local_matrix.setTranslate(quad->filters_origin.x(), quad->filters_origin.y()); 1099 local_matrix.setTranslate(quad->filters_origin.x(), quad->filters_origin.y());
1092 local_matrix.postScale(quad->filters_scale.x(), quad->filters_scale.y()); 1100 local_matrix.postScale(quad->filters_scale.x(), quad->filters_scale.y());
1093 gfx::Rect dst_rect = quad->filters.MapRect(quad->rect, local_matrix); 1101 params->filters = FiltersForPass(quad->render_pass_id);
1102 params->background_filters = BackgroundFiltersForPass(quad->render_pass_id);
1103 gfx::Rect dst_rect = params->filters
1104 ? params->filters->MapRect(quad->rect, local_matrix)
1105 : quad->rect;
1094 params->dst_rect.SetRect(static_cast<float>(dst_rect.x()), 1106 params->dst_rect.SetRect(static_cast<float>(dst_rect.x()),
1095 static_cast<float>(dst_rect.y()), 1107 static_cast<float>(dst_rect.y()),
1096 static_cast<float>(dst_rect.width()), 1108 static_cast<float>(dst_rect.width()),
1097 static_cast<float>(dst_rect.height())); 1109 static_cast<float>(dst_rect.height()));
1098 gfx::Transform quad_rect_matrix; 1110 gfx::Transform quad_rect_matrix;
1099 QuadRectTransform(&quad_rect_matrix, params->quad_to_target_transform, 1111 QuadRectTransform(&quad_rect_matrix, params->quad_to_target_transform,
1100 params->dst_rect); 1112 params->dst_rect);
1101 params->contents_device_transform = 1113 params->contents_device_transform =
1102 params->window_matrix * params->projection_matrix * quad_rect_matrix; 1114 params->window_matrix * params->projection_matrix * quad_rect_matrix;
1103 params->contents_device_transform.FlattenTo2d(); 1115 params->contents_device_transform.FlattenTo2d();
(...skipping 20 matching lines...) Expand all
1124 1136
1125 return true; 1137 return true;
1126 } 1138 }
1127 1139
1128 void GLRenderer::UpdateRPDQShadersForBlending( 1140 void GLRenderer::UpdateRPDQShadersForBlending(
1129 DrawRenderPassDrawQuadParams* params) { 1141 DrawRenderPassDrawQuadParams* params) {
1130 const RenderPassDrawQuad* quad = params->quad; 1142 const RenderPassDrawQuad* quad = params->quad;
1131 SkBlendMode blend_mode = quad->shared_quad_state->blend_mode; 1143 SkBlendMode blend_mode = quad->shared_quad_state->blend_mode;
1132 params->use_shaders_for_blending = 1144 params->use_shaders_for_blending =
1133 !CanApplyBlendModeUsingBlendFunc(blend_mode) || 1145 !CanApplyBlendModeUsingBlendFunc(blend_mode) ||
1134 ShouldApplyBackgroundFilters(quad) || 1146 ShouldApplyBackgroundFilters(quad, params->background_filters) ||
1135 settings_->force_blending_with_shaders; 1147 settings_->force_blending_with_shaders;
1136 1148
1137 if (params->use_shaders_for_blending) { 1149 if (params->use_shaders_for_blending) {
1138 DCHECK(params->frame); 1150 DCHECK(params->frame);
1139 // Compute a bounding box around the pixels that will be visible through 1151 // Compute a bounding box around the pixels that will be visible through
1140 // the quad. 1152 // the quad.
1141 gfx::Rect unclipped_rect; 1153 gfx::Rect unclipped_rect;
1142 params->background_rect = GetBackdropBoundingBoxForRenderPassQuad( 1154 params->background_rect = GetBackdropBoundingBoxForRenderPassQuad(
1143 params->frame, quad, params->contents_device_transform, 1155 params->frame, quad, params->contents_device_transform, params->filters,
1144 params->clip_region, params->use_aa, &unclipped_rect); 1156 params->background_filters, params->clip_region, params->use_aa,
1157 &unclipped_rect);
1145 1158
1146 if (!params->background_rect.IsEmpty()) { 1159 if (!params->background_rect.IsEmpty()) {
1147 // The pixels from the filtered background should completely replace the 1160 // The pixels from the filtered background should completely replace the
1148 // current pixel values. 1161 // current pixel values.
1149 if (blend_enabled()) 1162 if (blend_enabled())
1150 SetBlendEnabled(false); 1163 SetBlendEnabled(false);
1151 1164
1152 // Read the pixels in the bounding box into a buffer R. 1165 // Read the pixels in the bounding box into a buffer R.
1153 // This function allocates a texture, which should contribute to the 1166 // This function allocates a texture, which should contribute to the
1154 // amount of memory used by render surfaces: 1167 // amount of memory used by render surfaces:
1155 // LayerTreeHost::CalculateMemoryForRenderSurfaces. 1168 // LayerTreeHost::CalculateMemoryForRenderSurfaces.
1156 params->background_texture = 1169 params->background_texture =
1157 GetBackdropTexture(params->frame, params->background_rect); 1170 GetBackdropTexture(params->frame, params->background_rect);
1158 1171
1159 if (ShouldApplyBackgroundFilters(quad) && params->background_texture) { 1172 if (ShouldApplyBackgroundFilters(quad, params->background_filters) &&
1173 params->background_texture) {
1160 // Apply the background filters to R, so that it is applied in the 1174 // Apply the background filters to R, so that it is applied in the
1161 // pixels' coordinate space. 1175 // pixels' coordinate space.
1162 params->background_image = ApplyBackgroundFilters( 1176 params->background_image = ApplyBackgroundFilters(
1163 quad, params->background_texture.get(), 1177 quad, *params->background_filters, params->background_texture.get(),
1164 gfx::RectF(params->background_rect), gfx::RectF(unclipped_rect)); 1178 gfx::RectF(params->background_rect), gfx::RectF(unclipped_rect));
1165 if (params->background_image) { 1179 if (params->background_image) {
1166 params->background_image_id = 1180 params->background_image_id =
1167 skia::GrBackendObjectToGrGLTextureInfo( 1181 skia::GrBackendObjectToGrGLTextureInfo(
1168 params->background_image->getTextureHandle(true)) 1182 params->background_image->getTextureHandle(true))
1169 ->fID; 1183 ->fID;
1170 DCHECK(params->background_image_id); 1184 DCHECK(params->background_image_id);
1171 } 1185 }
1172 } 1186 }
1173 } 1187 }
1174 1188
1175 if (!params->background_texture) { 1189 if (!params->background_texture) {
1176 // Something went wrong with reading the backdrop. 1190 // Something went wrong with reading the backdrop.
1177 DCHECK(!params->background_image_id); 1191 DCHECK(!params->background_image_id);
1178 params->use_shaders_for_blending = false; 1192 params->use_shaders_for_blending = false;
1179 } else if (params->background_image_id) { 1193 } else if (params->background_image_id) {
1180 // Reset original background texture if there is not any mask 1194 // Reset original background texture if there is not any mask
1181 if (!quad->mask_resource_id()) 1195 if (!quad->mask_resource_id())
1182 params->background_texture.reset(); 1196 params->background_texture.reset();
1183 } else if (CanApplyBlendModeUsingBlendFunc(blend_mode) && 1197 } else if (CanApplyBlendModeUsingBlendFunc(blend_mode) &&
1184 ShouldApplyBackgroundFilters(quad)) { 1198 ShouldApplyBackgroundFilters(quad, params->background_filters)) {
1185 // Something went wrong with applying background filters to the backdrop. 1199 // Something went wrong with applying background filters to the backdrop.
1186 params->use_shaders_for_blending = false; 1200 params->use_shaders_for_blending = false;
1187 params->background_texture.reset(); 1201 params->background_texture.reset();
1188 } 1202 }
1189 } 1203 }
1190 // Need original background texture for mask? 1204 // Need original background texture for mask?
1191 params->mask_for_background = 1205 params->mask_for_background =
1192 params->background_texture && // Have original background texture 1206 params->background_texture && // Have original background texture
1193 params->background_image_id && // Have filtered background texture 1207 params->background_image_id && // Have filtered background texture
1194 quad->mask_resource_id(); // Have mask texture 1208 quad->mask_resource_id(); // Have mask texture
1195 DCHECK_EQ(params->background_texture || params->background_image_id, 1209 DCHECK_EQ(params->background_texture || params->background_image_id,
1196 params->use_shaders_for_blending); 1210 params->use_shaders_for_blending);
1197 } 1211 }
1198 1212
1199 bool GLRenderer::UpdateRPDQWithSkiaFilters( 1213 bool GLRenderer::UpdateRPDQWithSkiaFilters(
1200 DrawRenderPassDrawQuadParams* params) { 1214 DrawRenderPassDrawQuadParams* params) {
1201 const RenderPassDrawQuad* quad = params->quad; 1215 const RenderPassDrawQuad* quad = params->quad;
1202 // Apply filters to the contents texture. 1216 // Apply filters to the contents texture.
1203 if (!quad->filters.IsEmpty()) { 1217 if (params->filters) {
1218 DCHECK(!params->filters->IsEmpty());
1204 sk_sp<SkImageFilter> filter = RenderSurfaceFilters::BuildImageFilter( 1219 sk_sp<SkImageFilter> filter = RenderSurfaceFilters::BuildImageFilter(
1205 quad->filters, gfx::SizeF(params->contents_texture->size())); 1220 *params->filters, gfx::SizeF(params->contents_texture->size()));
1206 if (filter) { 1221 if (filter) {
1207 SkColorFilter* colorfilter_rawptr = NULL; 1222 SkColorFilter* colorfilter_rawptr = NULL;
1208 filter->asColorFilter(&colorfilter_rawptr); 1223 filter->asColorFilter(&colorfilter_rawptr);
1209 sk_sp<SkColorFilter> cf(colorfilter_rawptr); 1224 sk_sp<SkColorFilter> cf(colorfilter_rawptr);
1210 1225
1211 if (cf && cf->asColorMatrix(params->color_matrix)) { 1226 if (cf && cf->asColorMatrix(params->color_matrix)) {
1212 // We have a color matrix at the root of the filter DAG; apply it 1227 // We have a color matrix at the root of the filter DAG; apply it
1213 // locally in the compositor and process the rest of the DAG (if any) 1228 // locally in the compositor and process the rest of the DAG (if any)
1214 // in Skia. 1229 // in Skia.
1215 params->use_color_matrix = true; 1230 params->use_color_matrix = true;
(...skipping 2827 matching lines...) Expand 10 before | Expand all | Expand 10 after
4043 // The alpha has already been applied when copying the RPDQ to an IOSurface. 4058 // The alpha has already been applied when copying the RPDQ to an IOSurface.
4044 GLfloat alpha = 1; 4059 GLfloat alpha = 1;
4045 gl_->ScheduleCALayerSharedStateCHROMIUM(alpha, is_clipped, clip_rect, 4060 gl_->ScheduleCALayerSharedStateCHROMIUM(alpha, is_clipped, clip_rect,
4046 sorting_context_id, gl_transform); 4061 sorting_context_id, gl_transform);
4047 gl_->ScheduleCALayerCHROMIUM( 4062 gl_->ScheduleCALayerCHROMIUM(
4048 texture_id, contents_rect, ca_layer_overlay->background_color, 4063 texture_id, contents_rect, ca_layer_overlay->background_color,
4049 ca_layer_overlay->edge_aa_mask, bounds_rect, filter); 4064 ca_layer_overlay->edge_aa_mask, bounds_rect, filter);
4050 } 4065 }
4051 4066
4052 } // namespace cc 4067 } // namespace cc
OLDNEW
« no previous file with comments | « cc/output/gl_renderer.h ('k') | cc/output/gl_renderer_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698